自己动手搭建Banner轮播器
来源:互联网 发布:西门子1200plc编程 编辑:程序博客网 时间:2024/06/05 15:53
源代码引用地址:https://github.com/yiyibb/Zhihu
首先来看轮播效果图
整体是个recyclerview,头部布局为banner轮播图,此处的banner是继承Framelayout实现的。代码后面会具体说明,这里我们先看轮播图的xml文件的结构,总共三部分首先是背景图片,可实现自动滑动,另外是标题,还有最下面的 圆点,都会随着图片的移动而移动。
布局文件.xml
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/bannerLayout" android:layout_width="match_parent" android:layout_height="@dimen/space_200"> <com.yiyi.zhihu.ui.widget.Banner.BannerViewPaper android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/banner_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:gravity="left" android:maxLines="3" android:padding="@dimen/space_16" android:textColor="@color/colorWhite" android:textSize="@dimen/textSize_20" android:textStyle="bold" tools:text="图片轮播器" tools:textColor="@color/colorLightBlue"/> <LinearLayout android:id="@+id/indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal" android:layout_marginBottom="@dimen/space_10" android:layout_marginTop="@dimen/space_10" android:gravity="center" android:orientation="horizontal" > </LinearLayout> </FrameLayout>
看重点主要是BannerViewPaper,这个类仅仅继承了ViewPager,代码如下:
public class BannerViewPaper extends ViewPager { private boolean scrollable = true; public BannerViewPaper(Context context) { super(context); } public BannerViewPaper(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onTouchEvent(MotionEvent ev) { return this.scrollable && super.onTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return this.scrollable && super.onInterceptTouchEvent(ev); } public void setScrollable(boolean scrollable) { this.scrollable = scrollable; }}
滑动事件以及点击事件设定了scrollable。
接下来看重点banner,其实这也算是自定义viewgroup了,继承了framelayout,实现了滑动以及自动播放功能。看一下代码吧,emm,代码一如既往地多,但是我会一点点来分析,踏踏实实,耐耐心心,做技术本该如此,不是吗?
public class Banner extends FrameLayout implements ViewPager.OnPageChangeListener { private static final String TAG = "Banner"; private int indicatorSize; private int mIndicatorWidth; private int mIndicatorHeight; private int mIndicatorMargin; private int bannerStyle = BannerConfig.CIRCLE_INDICATOR; private int delayTime = BannerConfig.TIME; private int scrollTime = BannerConfig.DURATION; private boolean isAutoPlay = BannerConfig.IS_AUTO_PLAY; private boolean isScroll = BannerConfig.IS_SCROLL; private int mIndicatorSelectedResId = R.drawable.selected_radius; private int mIndicatorUnselectedResId = R.drawable.unselected_radius; private int titleHeight; private int titleBackground; private int titleTextColor; private int titleTextSize; private int count = 0; private int currentItem; private int lastPosition = 1; private Context mContext; private BannerPagerAdapter mAdapter; private List<View> imageViews; private List<ImageView> indicatorImages; private List imageUrls; private List<String> titles; private BannerViewPaper mViewPaper; private TextView bannerTitle; private LinearLayout indicator; private BannerScroller mScroller; private DisplayMetrics dm; private ImageLoaderInterface imageLoader; private OnBannerClickListener mOnBannerClickListener; private ViewPager.OnPageChangeListener mOnPageChangeListener; private WeakHandler handler = new WeakHandler(); public Banner(Context context) { this(context, null); } public Banner(Context context, AttributeSet attrs) { this(context, attrs, 0); } public Banner(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; titles = new ArrayList<>(); imageUrls = new ArrayList(); imageViews = new ArrayList<>(); indicatorImages = new ArrayList<>(); dm = context.getResources().getDisplayMetrics(); indicatorSize = dm.widthPixels / 80; initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { imageViews.clear(); View view = LayoutInflater.from(context).inflate(R.layout.banner, this, true); mViewPaper = (BannerViewPaper) view.findViewById(R.id.viewPager); bannerTitle = (TextView) view.findViewById(R.id.banner_title); indicator = (LinearLayout) view.findViewById(R.id.indicator); handleTypedArray(context, attrs); initViewPaperScroll(); } private void handleTypedArray(Context context, AttributeSet attrs) { if (attrs == null) { return; } TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Banner); mIndicatorWidth = typedArray.getDimensionPixelSize(R.styleable.Banner_indicator_width, indicatorSize); mIndicatorHeight = typedArray.getDimensionPixelSize(R.styleable.Banner_indicator_height, indicatorSize); mIndicatorMargin = typedArray.getDimensionPixelSize(R.styleable.Banner_indicator_margin, BannerConfig.PADDING_SIZE); mIndicatorSelectedResId = typedArray.getResourceId(R.styleable.Banner_indicator_drawable_selected, R.drawable.selected_radius); mIndicatorUnselectedResId = typedArray.getResourceId(R.styleable.Banner_indicator_drawable_unselected, R.drawable.unselected_radius); delayTime = typedArray.getInt(R.styleable.Banner_delay_time, BannerConfig.TIME); scrollTime = typedArray.getInt(R.styleable.Banner_scroll_time, BannerConfig.DURATION); isAutoPlay = typedArray.getBoolean(R.styleable.Banner_is_auto_play, BannerConfig.IS_AUTO_PLAY); titleBackground = typedArray.getColor(R.styleable.Banner_title_background, BannerConfig.TITLE_BACKGROUND); titleHeight = typedArray.getDimensionPixelSize(R.styleable.Banner_title_height, BannerConfig.TITLE_HEIGHT); titleTextColor = typedArray.getColor(R.styleable.Banner_title_textcolor, BannerConfig.TITLE_TEXT_COLOR); titleTextSize = typedArray.getDimensionPixelSize(R.styleable.Banner_title_textsize, BannerConfig.TITLE_TEXT_SIZE); typedArray.recycle(); } private void initViewPaperScroll() { try { Field mField = ViewPager.class.getDeclaredField("mScroller"); mField.setAccessible(true); mScroller = new BannerScroller(mViewPaper.getContext()); mScroller.setDuration(scrollTime); mField.set(mViewPaper, mScroller); } catch (Exception e) { Log.e(TAG, e.getMessage()); } } public Banner isAutoPlay(boolean isAutoPlay) { this.isAutoPlay = isAutoPlay; return this; } public Banner setImageLoader(ImageLoaderInterface imageLoader) { this.imageLoader = imageLoader; return this; } public Banner setDelayTime(int delayTime) { this.delayTime = delayTime; return this; } public Banner setBannerTitles(List<String> titles) { this.titles = titles; bannerTitle.setText(titles.get(0)); return this; } public Banner setImages(List<?> imageUrls) { this.imageUrls = imageUrls; this.count = imageUrls.size(); return this; } public void update(List<?> imageUrls, List<String> titles) { this.imageUrls.clear(); this.titles.clear(); this.imageUrls.addAll(imageUrls); this.titles.addAll(titles); this.count = this.imageUrls.size(); start(); } public void update(List<?> imageUrls) { this.imageUrls.clear(); this.imageUrls.addAll(imageUrls); this.count = this.imageUrls.size(); start(); } public Banner start() { setImagesList(imageUrls); if (isAutoPlay) { startAutoPlay(); } return this; } private void createIndicator() {//滚动的圆点 indicatorImages.clear(); indicator.removeAllViews(); for (int i = 0; i < count; ++i) { ImageView imageView = new ImageView(mContext); imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIndicatorWidth, mIndicatorHeight); params.leftMargin = mIndicatorMargin; params.rightMargin = mIndicatorMargin; if (i == 0) { imageView.setImageResource(mIndicatorSelectedResId); } else { imageView.setImageResource(mIndicatorUnselectedResId); } indicatorImages.add(imageView); indicator.addView(imageView, params); } } private void setData() { currentItem = 1; if (mAdapter == null) { mAdapter = new BannerPagerAdapter(); } mViewPaper.setAdapter(mAdapter); mViewPaper.setFocusable(true); mViewPaper.setCurrentItem(currentItem); mViewPaper.addOnPageChangeListener(this); if (isScroll && count > 1) { mViewPaper.setScrollable(true); } else { mViewPaper.setScrollable(false); } } private void setImagesList(List<?> imageUrls) { if (imageUrls == null || imageUrls.size() <= 0) { Log.e(TAG, "please set the images data"); return; } imageViews.clear(); createIndicator();//滚动小圆球 for (int i = 0; i <= count + 1; ++i) { View imageView = null; if (imageLoader != null) { imageView = imageLoader.creteImageView(mContext); } if (imageView == null) { imageView = new ImageView(mContext); } ((ImageView)imageView).setScaleType(ImageView.ScaleType.CENTER_CROP); Object url = null; // 无限轮播实现原理 if (i == 0) { url = imageUrls.get(count - 1); } else if (i == count + 1) { url = imageUrls.get(0); } else { url = imageUrls.get(i - 1); } imageViews.add(imageView); if (imageLoader != null) { imageLoader.displayImage(mContext, url, imageView); } else { Log.e(TAG, "please set image loader"); } } setData(); } public void startAutoPlay() { handler.removeCallbacks(task); handler.postDelayed(task, delayTime); } public void stopAutoPlay() { handler.removeCallbacks(task); } private final Runnable task = new Runnable() { @Override public void run() { if (count > 1 && isAutoPlay) { currentItem = currentItem % (count + 1) + 1; if (currentItem == 1) { mViewPaper.setCurrentItem(currentItem); handler.postDelayed(task, delayTime); Log.i("困了1",mViewPaper.getCurrentItem()+""); } else if (currentItem == count+1) { //mViewPaper.setCurrentItem(currentItem,false); handler.post(task); Log.i("困了1",mViewPaper.getCurrentItem()+""); } else { mViewPaper.setCurrentItem(currentItem); handler.postDelayed(task, delayTime); Log.i("困了2",mViewPaper.getCurrentItem()+""); } } } }; /** * 返回真实的位置 * * @param position * @return 下标从0开始 */ public int toRealPosition(int position) { int realPosition = (position - 1) % count; if (realPosition < 0) { realPosition += count; } return realPosition; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (isAutoPlay) { int action = ev.getAction(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_OUTSIDE) { startAutoPlay(); } else if (action == MotionEvent.ACTION_DOWN) { stopAutoPlay(); } } return super.dispatchTouchEvent(ev); } class BannerPagerAdapter extends PagerAdapter { @Override public int getCount() { return imageViews.size(); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public Object instantiateItem(ViewGroup container, final int position) { container.addView(imageViews.get(position)); View view = imageViews.get(position); if (mOnBannerClickListener != null) { view.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { mOnBannerClickListener.OnBannerClick(toRealPosition(position)); } }); } return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View)object); } } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (mOnPageChangeListener != null) {//不执行 mOnPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels); Log.i("梦醒","对象为空1"); } } @Override public void onPageSelected(int position) { if (mOnPageChangeListener != null) {//不执行 mOnPageChangeListener.onPageSelected(position); Log.i("梦醒","对象为空2"); } indicatorImages.get((lastPosition - 1 + count) % count).setImageResource(mIndicatorUnselectedResId); indicatorImages.get((position - 1 + count) % count).setImageResource(mIndicatorSelectedResId); lastPosition = position; if (position == 0) position = count; if (position > count) position = 1; bannerTitle.setText(titles.get(position - 1)); } @Override public void onPageScrollStateChanged(int state) { if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageScrollStateChanged(state); } currentItem = mViewPaper.getCurrentItem(); switch (state) { /* SCROLL_STATE_DRAGGING(1)表示用户手指“按在屏幕上并且开始拖动”的状态(手指按下但是还没有拖动的时候还不是这个状态, 只有按下并且手指开始拖动后log才打出。) SCROLL_STATE_IDLE(0)滑动动画做完的状态。 SCROLL_STATE_SETTLING(2)在“手指离开屏幕”的状态。*/ case 0://No operation /* if (currentItem == 0) { mViewPaper.setCurrentItem(count,false); } else if (currentItem == count + 1) { mViewPaper.setCurrentItem(1, false); } Log.i("梦醒0",mViewPaper.getCurrentItem()+"");*/ break; case 1://start Sliding正在滑动 /*if (currentItem == count + 1) { mViewPaper.setCurrentItem(1, false); } else if (currentItem == 0) { mViewPaper.setCurrentItem(count, false); } Log.i("梦醒1",mViewPaper.getCurrentItem()+"");*/ break; case 2://end Sliding /*if (currentItem == 0) { mViewPaper.setCurrentItem(count,false); } else if (currentItem == count + 1) { mViewPaper.setCurrentItem(1, false); } Log.i("梦醒2",mViewPaper.getCurrentItem()+"");*/ break; } } public void setOnPageChangeListener(ViewPager.OnPageChangeListener onPageChangeListener) { mOnPageChangeListener = onPageChangeListener; } public Banner setOnBannerClickListener(OnBannerClickListener listener) { this.mOnBannerClickListener = listener; return this; } public interface OnBannerClickListener { public void OnBannerClick(int position); }}
找关键,挑重点,重要的几个方法就这么多
initView();
startAutoPlay();
task;
介绍一下整体流程,说到底banner的实现使用就是viewpager,利用定时让viewpager自动播放,难点是无限轮播,这里我也只能算是看懂了代码,,但是不能理会他的精髓,直接让我写,我还是写不出来,这就是大神的能力吧。
首先initview初始化布局文件,这里面有两个方法一个是 handleTypedArray用于配置布局控件中的风格,高度长度,背景等等。另一个方法是initViewPaperScroll()这个方法是利用Java反射机制控制viewpager的滑动时间。再往后边看就是一些设定参数的方法,没啥可讲的。
后面重点是start方法,这个方法里面先是初始化了数据。关键代码如下:
for (int i = 0; i <= count + 1; ++i) { View imageView = null; if (imageLoader != null) { imageView = imageLoader.creteImageView(mContext); } if (imageView == null) { imageView = new ImageView(mContext); } ((ImageView)imageView).setScaleType(ImageView.ScaleType.CENTER_CROP); Object url = null; // 无限轮播实现原理 if (i == 0) { url = imageUrls.get(count - 1); } else if (i == count + 1) { url = imageUrls.get(0); } else { url = imageUrls.get(i - 1); } imageViews.add(imageView); if (imageLoader != null) { imageLoader.displayImage(mContext, url, imageView); } else { Log.e(TAG, "please set image loader"); } }
这个轮播图片总数为5,也就是count为5。画了一张草图
图上面是从网络上获取到的url集合,图下面是viewpager对应的imageview的六张图片。下面来看runable延时任务,每隔一段时间就会执行一次,但是由于这里是无限轮播,所以会有一些特殊处理
private final Runnable task = new Runnable() { @Override public void run() { if (count > 1 && isAutoPlay) { currentItem = currentItem % (count + 1) + 1; if (currentItem == 1) { mViewPaper.setCurrentItem(currentItem); handler.postDelayed(task, delayTime); } else if (currentItem == count+1) { handler.post(task); } else { mViewPaper.setCurrentItem(currentItem); handler.postDelayed(task, delayTime); } } } };
这里其实是一个循环,因为 currentItem每次都会除以6取余加1,初始化currentItem为1,需要注意的是 currentItem是从imageviews中取的值。 currentItem会按照1—>2—>3—>4—>5—>6—>1这样循环下去。
当照片为imageviews中的最后一张时,task任务会直接执行下一次任务不做延时,也就是直接蹦到imageviews的第一张。这时候您如果仔细看的话会发现imageviews的第0张图片并没有什么卵用啊,其实这个问题是隐藏的,刚开始我也没注意到。当currentItem为1时,也就是imageviews中的第一张照片,这时候用户要是向左滑动,就会蹦到imageviews的第0张图片。这时候再来看,按照正常逻辑走 currentItem下一个又会变成1,不得不佩服作者的逻辑,这种可以被算上算法了吧,左右的操作都在这个循环里。
Tip:我对作者的源代码稍稍改变了一下,因为他这里有点小小的问题,viewpager在自动滑动的情况下,当用户向左滑动viewpager一直到倒数第一张图片,这时候用户松手,轮播会直接跳转到第二张图片然后继续滑动。我修改了onPageScrollStateChanged和task两个方法,您可以仔细研究一下作者的源码。https://github.com/yiyibb/Zhihu
还有一些次要的方法:
createIndicator:用于底部圆点的滚动,这个圆点滚动效果其实是在onPageSelected时候开始变化的,道理比较简单,也是跟上面一样像一个循环。
onPageScrollStateChanged:这个方法原作者是写了一些代码的,针对的也是用户滑动事件,简单说说这个方法对应着三种状态
SCROLL_STATE_DRAGGING(1)表示用户手指“按在屏幕上并且开始拖动”的状态(手指按下但是还没有拖动的时候还不是这个状态,只有按下并且手指开始拖动后log才打出。)
SCROLL_STATE_IDLE(0)滑动动画做完的状态。
SCROLL_STATE_SETTLING(2)在“手指离开屏幕”的状态。
源代码作者写的代码我感觉是非必要的,已经注释掉了,也可能是自己没能领会到吧。
dispatchTouchEvent:这个是触摸事件,用于拦截用户的滑动事件,没啥可讲的
OnBannerClickListener:监听回调接口
感兴趣的可以关注我最新开的公众号,重在分享!!微信搜索 开发Android的小学生
- 自己动手搭建Banner轮播器
- banner轮播器
- 自己动手搭建WAMP
- 自己动手搭建搜索工具
- 自己动手搭建MVC之一
- 自己动手搭建GitLab
- 自己动手搭建GitLab
- 自己动手搭建VPN服务器
- 《自己动手做操作系统》环境搭建
- 《自己动手做操作系统》环境搭建
- 自己动手写操作系统--搭建环境
- 自己动手搭建缺陷Web App
- 自己动手搭建免费VoIP服务器
- 自己动手搭建Android开发环境
- 自己动手搭建软件WiFi热点
- 自己动手一步步搭建repo服务器
- 自己动手搭建MVC之二
- 自己动手搭建MVC之三
- 计算机视觉 | Python OpenCV 3 使用背景减除进行目标检测
- 详解recyclerview的分割线
- Retrofit的简单用法
- RecyclerView的点击事件
- recyclerview万能适配器用法以及源码分析
- 自己动手搭建Banner轮播器
- 记录一家坑爹的公司
- 将博客搬至CSDN
- 图解TCP/IP笔记——OSI参考模型
- 图解TCP/IP网络构成
- Java项目后台打包jar包执行报错“没有主清单属性”
- android其实很简单--双层Toolbar上拉隐藏实现
- TCP/IP协议分层模型
- Android studio升级3.0以后添加依赖失败