打造一体式轮播广告条

521 阅读5分钟

这是我参与11月更文挑战的第24天,活动详情查看:2021最后一次更文挑战

  在生活中,我们不难发现一些电商、社团类的 App 都会有一些轮播的广告条出现,通过轮播图能够快速实现重要信息展示,所以是 App 开发中比较常见的一个功能使用。轮播广告条在应用中非常广泛的使用,这次在看公司一个项目时,发现多处都使用到轮播广告条的功能,看到了很多重复的代码,所以萌生了整合的想法。先来看看常见的轮播广告条的样式。

common

通过上面我们也可以发现,常见的轮播广告条的组成:

  • 一个ViewPager控件
  • 一个指示器控件

所以在整合的过程中,我就是将ViewPager和指示器部分封装起来了。根据上面的布局,所以我们继承RelativeLayout进行布局。所以我们定义类ViewPagerBarnner

	public class ViewPagerBarnner extends RelativeLayout implements OnPageChangeListener {
		/**
		 * ViewPager对象
		 */
		private ViewPager viewPager;
		/**
		 * 指示器
		 */
		private LinearLayout indicatorView;
		private Context context;
		/**
		 * 图片url地址
		 */
		private List<String> imageUrls = new ArrayList<String>();
		/**
		 * 获取的ImageView对象集合
		 */
		private List<ImageView> imageViews = new ArrayList<ImageView>();
		/**
		 * 点击ViewPager中ImageView的回调事件
		 */
		private ViewPagerClick viewPagerClick;
		/**
		 * 指示器的默认大小
		 */
		private float indicatorSize = 15;
		/**
		 * 指示器的drawable对应的资源id
		 */
		private int idBackgroud;
		/**
		 * 指示器之间的距离
		 */
		private float indicatorMargin = 20;
	
		...
	 }

上面就是我们需要的控件,我们定义了Viewpager和一个LinearLayout,其中LinearLayout用于存储我们的指示器。为了提高定制性,我们自定义了三个属性,如下:

	<?xml version="1.0" encoding="utf-8"?>
	<resources>
	    <declare-styleable name="ViewPager">
			<attr name="containerHeight" format="dimension"/>			<!-- 容器的高度 -->
	        <attr name="indicatorSize" format="dimension"/>  		<!-- 指示器大小 -->
	        <attr name="indicatorBackgroud" format="reference"/>	<!-- 指示器背景,可以说drawable对象 -->
	        <attr name="indicatorMargin" format="dimension"/>		<!-- 指示器之间的间隔 -->
	    </declare-styleable>  
	</resources>

有了自定义的属性,我们在构造函数中进行获取设定的值。这里,我们定义背景为一个reference类型,在使用的过程中我通过type

		public ViewPagerBarnner(Context context, AttributeSet attrs) {
			super(context, attrs);
			this.context = context;
			initViews();
	        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ViewPager);
	        indicatorSize = typedArray.getDimension(R.styleable.ViewPager_indicatorSize, 10);
	        indicatorMargin = typedArray.getDimension(R.styleable.ViewPager_indicatorMargin, 15);
	        idBackgroud = typedArray.getResourceId(R.styleable.ViewPager_indicatorBackgroud, 0);
			containerHeight = typedArray.getDimension(R.styleable.ViewPager_containerHeight, 20);
	        typedArray.recycle();
		}

		/**
		 * 初始化View的视图
		 */
		private void initViews(){
			viewPager = new ViewPager(context);
			LayoutParams viewPagerParams = new LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, 
													RelativeLayout.LayoutParams.MATCH_PARENT);
			viewPager.setLayoutParams(viewPagerParams);
			viewPager.setAdapter(viewPagerAdapter);
			viewPager.setOnPageChangeListener(this);
			
			indicatorView = new LinearLayout(context);
			indicatorView.setOrientation(LinearLayout.HORIZONTAL);
			LayoutParams layoutParams = new LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,(int)containerHeight);
			layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
			layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
			indicatorView.setLayoutParams(layoutParams);
			addView(viewPager);
			addView(indicatorView);
		}

我们初始化ViewPager和LinearLayout,同时对它们进行布局。将LinearLayout放到最底部。接下来就是设置我们的监听事件以及为ViewPager设置Adapter。

		/**
		 * ViewPager的适配器
		 */
		private PagerAdapter viewPagerAdapter = new PagerAdapter() {
			
			@Override
			public boolean isViewFromObject(View view, Object object) {
				return view == object;
			}
			
			@Override
			public int getCount() {
				return imageUrls == null ? 0 : imageUrls.size();
			}
	
			@Override
			public void destroyItem(ViewGroup container, int position, Object object) {
				container.removeView((View) object);
			}
	
			@Override
			public void finishUpdate(ViewGroup container) {
			}
	
			@Override
			public int getItemPosition(Object object) {
				return super.getItemPosition(object);
			}
	
			@Override
			public Object instantiateItem(ViewGroup container, int position) {
				ImageView imageView = null;
				if(imageViews != null && imageViews.size() > 0){
					imageView = imageViews.get(position);
					imageView.setOnClickListener(new OnClickListener() {
						
						@Override
						public void onClick(View view) {
							if(viewPagerClick != null){
								viewPagerClick.viewPagerOnClick(view);
							}
						}
					});
				}
				container.addView(imageView);
				return imageView;
			}
		};
		
		@Override
		public void onPageScrollStateChanged(int arg0) {
		}
	
		@Override
		public void onPageScrolled(int arg0, float arg1, int arg2) {
		}
	
		@Override
		public void onPageSelected(int location) {
			setSelectPage(location);
		}

至此,整体的框架已经完成,然后就是我们为Adapter赋予数据。具体处理如下:

		/**
		 * 从url地址创建imageview对象,同时初始化指示器
		 */
		private void createImageView(List<String> imageUrlList){
			if(imageUrlList != null && imageUrlList.size() > 0){
				ImageView imageView;
				View pointView;
				for(String url : imageUrlList){
					imageView = new ImageView(context);
					imageView.setScaleType(ScaleType.FIT_XY);
					ImageLoader.getInstance().displayImage(url, imageView);
					imageView.setTag(url);
					imageViews.add(imageView);
					
					pointView = new View(context);
					LinearLayout.LayoutParams params = new LinearLayout.LayoutParams((int)(indicatorSize),(int)(indicatorSize));
					params.rightMargin = (int)indicatorMargin;
					pointView.setLayoutParams(params);
					pointView.setBackgroundDrawable(context.getResources().getDrawable(idBackgroud));
					pointView.setEnabled(false);
					indicatorView.addView(pointView);
				}
				setSelectPage(0);
			}
		}
		
		/**
		 * 设置当前选中页面
		 * @param position
		 */
		private void setSelectPage(int position){
			for(int index=0; index < indicatorView.getChildCount();index++){
				if(position == index){
					indicatorView.getChildAt(index).setEnabled(true);
				}else{
					indicatorView.getChildAt(index).setEnabled(false);
				}
			}
		}
		
		/**
		 * 设置图片的地址,从网络加载图片
		 * @param imageUrls
		 */
		public void addImageUrls(List<String> imageUrls) {
			this.imageUrls.addAll(imageUrls);
			createImageView(imageUrls);
			viewPagerAdapter.notifyDataSetChanged();
		}

至此,就完成了基本的广告条展示功能。我们看下MainActivity和布局中的使用。

		<com.lcwang.androidviews.ViewPagerBarnner
		    android:id="@+id/viewPager"
		    android:layout_height="160dp"
		    android:layout_width="match_parent"
		    viewpager:indicatorSize="8dp"
		    viewpager:indicatorMargin="10dp"
		    viewpager:indicatorBackgroud="@drawable/indicator_backgroud"
		    />


	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ViewPagerBarnner viewPagerBarnner = (ViewPagerBarnner) findViewById(R.id.viewPager);
		List<String> url = new ArrayList<String>();
		url.add("http://pic1.nipic.com/2008-12-25/2008122510134038_2.jpg");
		url.add("http://pic3.nipic.com/20090525/2416945_231841034_2.jpg");
		url.add("http://img3.3lian.com/2013/s1/20/d/57.jpg");
		url.add("http://pic1.nipic.com/2008-11-13/2008111384358912_2.jpg");
		url.add("http://img.61gequ.com/allimg/2011-4/201142614314278502.jpg");
		viewPagerBarnner.addImageUrls(url);
		viewPagerBarnner.setViewPagerClick(new ViewPagerClick() {
			
			@Override
			public void viewPagerOnClick(View view) {
				Toast.makeText(MainActivity.this, view.getTag().toString(), Toast.LENGTH_SHORT).show();
			}
		});
	}

我们自定义指示器的样式:

	<?xml version="1.0" encoding="utf-8"?>
	<selector xmlns:android="http://schemas.android.com/apk/res/android" >
	    <item android:state_enabled="true">
	        <shape android:shape="oval">
	            <size android:width="3dp" android:height="3dp"/>
	            <solid android:color="#00FF00"/>
	        </shape>
	    </item>
		<item android:state_enabled="false">
		    <shape android:shape="oval">
		        <size android:width="3dp" android:height="3dp"/>
	            <solid android:color="#FFFFFF"/>
		    </shape>
		</item>
	</selector>

first

通过上面的效果图,我们发现基本的效果已经有了,但是还没有实现无限循环以及自动播放的功能。所以接下来我们就是实现无限循环和自动播放的功能。网上流行的两种实现方法,我们先看一种,我画了一个草图:

fliper

为了方便我们增加头尾,我将存放url和imageview的集合改为LinkedList。

	private LinkedList<String> imageUrls = new LinkedList<String>();
	private LinkedList<ImageView> imageViews = new LinkedList<ImageView>();

主要的判断处理就是增加头尾处,这里说一点,在为ImageView的集合增加首尾的时候,为什么重新获取而不是利用集合中已经存在的,这是因为如果我们将该对象进行利用后,持有该对象的引用,然后在切换时,报该ImageView已经有父容器了。(这是我理解的)

	@Override
	public void onPageSelected(int location) {
		if(location == this.imageUrls.size() -1){
			location = 1;
			viewPager.setCurrentItem(location,false);
		}else if(location == 0){
			location = this.imageUrls.size() -2;
			viewPager.setCurrentItem(location,false);
		}
		currentPostion = location;
		setSelectPage(location - 1);
	}
	
	
	/** 
	 * 从url地址创建imageview对象,同时初始化指示器
	 */
	private void createImageView(List<String> imageUrlList){
		if(imageUrlList != null && imageUrlList.size() > 0){
			ImageView imageView;
			View pointView;
			//清除头尾
			if(imageViews.size() > 1){
				imageViews.removeFirst();
				imageViews.removeLast();
			}
			for(String url : imageUrlList){
				imageView = new ImageView(context);
				imageView.setScaleType(ScaleType.FIT_XY);
				ImageLoader.getInstance().displayImage(url, imageView);
				imageView.setTag(url);
				imageViews.add(imageView);
				
				pointView = new View(context);
				LinearLayout.LayoutParams params = new LinearLayout.LayoutParams((int)(indicatorSize),(int)(indicatorSize));
				params.rightMargin = (int)indicatorMargin;
				pointView.setLayoutParams(params);
				pointView.setBackgroundDrawable(context.getResources().getDrawable(idBackgroud));
				pointView.setEnabled(false);
				indicatorView.addView(pointView);
			}
			//增加头尾
			if(imageViews.size() > 1){
				ImageView ivFirst = new ImageView(context);
				ImageView ivLast = new ImageView(context);
				ImageLoader.getInstance().displayImage(imageUrls.getLast(),ivLast);
				ImageLoader.getInstance().displayImage(imageUrls.getFirst(),ivFirst);
				imageViews.addFirst(ivFirst);
				imageViews.addLast(ivLast);
			}
			viewPagerAdapter.notifyDataSetChanged();
			viewPager.setCurrentItem(1);
		}
	}
	
	/**
	 * 设置当前选中页面
	 * @param position
	 */
	private void setSelectPage(int position){
		for(int index=0; index < indicatorView.getChildCount();index++){
			if(position == index){
				indicatorView.getChildAt(index).setEnabled(true);
			}else{
				indicatorView.getChildAt(index).setEnabled(false);
			}
		}
	}
	
	/**
	 * 设置图片的地址,从网络加载图片
	 * @param imageUrls
	 */
	public void addImageUrls(List<String> imageUrls) {
		if(this.imageUrls.size() > 1){//清除头尾
			this.imageUrls.removeFirst();
			this.imageUrls.removeLast();
		}
		this.imageUrls.addAll(imageUrls);
		if(this.imageUrls.size() >1){//增加头尾
			String first = this.imageUrls.getFirst();
			this.imageUrls.addFirst(this.imageUrls.getLast());
			this.imageUrls.addLast(first);
		}
		createImageView(imageUrls);
	}

这样就可以实现了ViewPager的循环,同时我们可以通过addImageUrls方法进行新增页。实现了循环,就开始自动播放,自动播放的实现无外乎几种:

  • 1、定时器:Timer
  • 2、开子线程 while true 循环
  • 3、ColckManager
  • 4、 用handler 发送延时信息,实现循环

我们通过Handler进行实现。

	private Handler mHandler = new Handler(){

		@Override
		public void handleMessage(Message msg) {
			viewPager.setCurrentItem(currentPostion + 1);
			mHandler.sendEmptyMessageDelayed(0, 3000);
		}
	};

	/**
	 * 初始化View的视图
	 */
	private void initViews(){
		...
		mHandler.sendEmptyMessageDelayed(0, 3000);
	}

这样就完成了一个组合的ViewPager开发。来瞄一眼效果:

second

至此,这个控件就完成了,可以很方便使用,在也不用去搞很多逻辑判断了。这里面加载网络图片使用的ImageLoader库,需要添加,或者改源码自己修改。