1. 程式人生 > >Android自定義控制元件——仿淘寶、網易、彩票等廣告條、Banner的製作

Android自定義控制元件——仿淘寶、網易、彩票等廣告條、Banner的製作


最近翻看以前的某專案時,發現了一個極其常用的效果——廣告條,或者也稱不上自定義元件,但是使用頻率還是相當普遍的。

開啟市面上各大App主介面,或多或少會出現這樣的東西,甚至一個應用中出現N多個,這種展示廣告的效果,不僅動態效果好,而且眾所周知的“不佔屏”,想想在手機裝置這麼小的螢幕尺寸下,能放下幾頁甚至十幾頁的廣告迴圈播放,就知道這種廣告的使用頻率之大了。以下是我收集的部分APP中使用的效果截圖:

這些“千萬億”級別的APP都在使用的效果,為什麼我們不能效仿追隨一下呢,那下面我就開始動手做一個自己的廣告條;

要求如下:1,實現多圖展示

                    2,實現手勢切換

                    3,廣告圖片與廣告標語同時切換

                   4,迴圈切換,定時迴圈播放

以下是我的專案結構:

廣告條實際上用的是ViewPager來做的,佈局中僅僅放了一個ViewPager而已,其它的圖片切換都是用ViewPager來展示的,佈局如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="200dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@id/viewpager"
        android:background="#33000000"
        android:orientation="vertical"
        android:padding="5dp" >

        <TextView
            android:id="@+id/tv_image_description"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="鞏俐不低俗,我們也不低俗"
            android:textColor="@android:color/white" />

        <LinearLayout
            android:id="@+id/ll_point_group"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="5dp"
            android:orientation="horizontal" >
        </LinearLayout>
    </LinearLayout>

</RelativeLayout>

上面的ViewPager用來顯示廣告圖片,下面的LinearLayout嵌在ViewPager底部,實現陰影效果。裡面包括TextView來顯示廣告標語,和一個LinearLayout來顯示廣告切換狀態指示點。寫完佈局,就可以為這個ViewPager載入資料,增加動態效果了,主要程式碼如下,註釋清晰:
package com.example.banner;

import java.util.ArrayList;
import java.util.List;

import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.LinearLayout.LayoutParams;
import android.app.Activity;

public class MainActivity extends Activity {

	private List<ImageView> mImageList;
	/** 廣告條正下方的標語 */
	private String[] imageDescriptionArray = { //
	"鞏俐不低俗,我就不能低俗", //
			"撲樹又回來啦!再唱經典老歌引萬人大合唱", //
			"揭祕北京電影如何升級", //
			"樂視網TV版大派送", //
			"熱血屌絲的反殺" };
	/** 記錄上一次點的位置,預設為0 */
	private int previousPointEnale = 0;
	private ViewPager mViewPager;
	private LinearLayout llPointGroup;
	private TextView tvDescription;
	/** 記錄是否停止迴圈播放 */
	private boolean isStop = false;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		init();
		// 開啟子執行緒,讓廣告條以2秒的頻率迴圈播放
		new Thread(new Runnable() {

			@Override
			public void run() {

				while (!isStop) {
					SystemClock.sleep(2000);
					runOnUiThread(new Runnable() {
						public void run() {
							mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);
						}
					});
				}
			}
		}).start();
	}

	private void init() {
		llPointGroup = (LinearLayout) findViewById(R.id.ll_point_group);
		tvDescription = (TextView) findViewById(R.id.tv_image_description);
		mImageList = new ArrayList<ImageView>();
		int[] imageIds = new int[] { R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e };
		ImageView mImageView;
		LayoutParams params;
		// 初始化廣告條資源
		for (int id : imageIds) {
			mImageView = new ImageView(this);
			mImageView.setBackgroundResource(id);
			mImageList.add(mImageView);

			// 初始化廣告條正下方的"點"
			View dot = new View(this);
			dot.setBackgroundResource(R.drawable.point_background);
			params = new LayoutParams(5, 5);
			params.leftMargin = 10;
			dot.setLayoutParams(params);
			dot.setEnabled(false);
			llPointGroup.addView(dot);
		}
		mViewPager = (ViewPager) findViewById(R.id.viewpager);
		mViewPager.setAdapter(new MyAdapter());

		// 設定廣告條跳轉時,廣告語和狀態語的變化
		mViewPager.setOnPageChangeListener(new MyListener());

		// 初始化廣告條,當前索引Integer.MAX_VALUE的一半
		int index = (Integer.MAX_VALUE / 2) - (Integer.MAX_VALUE / 2 % mImageList.size());
		mViewPager.setCurrentItem(index); // 設定當前選中的Page,會觸發onPageChangListener.onPageSelected方法
	}

	private class MyListener implements OnPageChangeListener {

		@Override
		public void onPageScrollStateChanged(int arg0) {
			// TODO Auto-generated method stub

		}

		@Override
		public void onPageScrolled(int arg0, float arg1, int arg2) {
			// TODO Auto-generated method stub

		}

		@Override
		public void onPageSelected(int arg0) {
			// 獲取新的位置
			int newPosition = arg0 % imageDescriptionArray.length;
			// 設定廣告標語
			tvDescription.setText(imageDescriptionArray[newPosition]);
			// 消除上一次的狀態點
			llPointGroup.getChildAt(previousPointEnale).setEnabled(false);
			// 設定當前的狀態點“點”
			llPointGroup.getChildAt(newPosition).setEnabled(true);
			// 記錄位置
			previousPointEnale = newPosition;
		}

	}

	/**
	 * ViewPager資料介面卡
	 */
	private class MyAdapter extends PagerAdapter {

		@Override
		public int getCount() {
			// 將viewpager頁數設定成Integer.MAX_VALUE,可以模擬無限迴圈
			return Integer.MAX_VALUE;
		}

		/**
		 * 複用物件 true 複用view false 複用的是Object
		 */
		@Override
		public boolean isViewFromObject(View arg0, Object arg1) {
			// TODO Auto-generated method stub
			return arg0 == arg1;
		}

		/**
		 * 銷燬物件
		 * 
		 * @param position
		 *            被銷燬物件的索引位置
		 */
		@Override
		public void destroyItem(ViewGroup container, int position, Object object) {
			container.removeView(mImageList.get(position % mImageList.size()));
		}

		/**
		 * 初始化一個物件
		 * 
		 * @param position
		 *            初始化物件的索引位置
		 */
		@Override
		public Object instantiateItem(ViewGroup container, int position) {
			container.addView(mImageList.get(position % mImageList.size()));
			return mImageList.get(position % mImageList.size());
		}

	}

	@Override
	protected void onDestroy() {
		// activity銷燬時候,關閉迴圈播放
		isStop = true;
		super.onDestroy();
	}

}

         需要注意的是,為了達到廣告條迴圈播放的效果,故不能將ViewPager所展示的總數設定較小的定值,這樣若ViewPager劃過這個定值的時候,頁面會定住,使用者體驗就不太理想化了,為了能達到這個迴圈的效果,只能將ViewPager展示總數設定成一個很大的值,以便來給使用者造成無限迴圈的假象。那麼這個值該取多大合適呢?思前想後,覺得在PagerAdapter的getCount方法中,返回Integer.MAX_VALUE這個值,這個值2147483647,無論如何使用者也不可以拿手機沒事劃上個好幾億次吧。

       到此還要注意的地方就是,因為getCount中返回Integer.MAX_VALUE這麼大數值,為了達到有圖迴圈的效果,避免Bug,所以其後每次涉及到position索引的地方都得用position和資源尺度取餘的結果。

      此外,在“點”的初始化的時候,應當設定“點”的索引為int index = (Integer.MAX_VALUE / 2) - (Integer.MAX_VALUE / 2 % mImageList.size());

而不能簡單設定成0,若是設定成0,就無法制造出迴圈播放的“假象”,不信試試設定0,往左滑動。

關於“點”的資源,沒有用到圖片,下面是資原始碼,貼出來:

廣告條獲得焦點:point_bg_enable.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval" >

    <corners android:radius="0.5dp" />

    <solid android:color="#AAFFFFFF" />

</shape>

廣告條普通樣式:point_bg_normal.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval" >

    <corners android:radius="0.5dp" />

    <solid android:color="#55000000" />

</shape>

廣告條的狀態選擇器:point_background.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/point_bg_enable" android:state_enabled="true"></item>
    <item android:drawable="@drawable/point_bg_normal" android:state_enabled="false"></item>

</selector>
以下是效果圖:

最後,還需要實現廣告的自動迴圈播放,這個很簡單,只要開啟一個新執行緒,線上程中每隔2000ms迴圈更新一下ViewPager就行。就是在ViewPager中獲取當前展示的Item的索引,加上1之後,設定展示這個值即可。還得注意程式的嚴謹性啊,當activity銷燬的時候,這個新執行緒裡負責迴圈播放的程式碼是徐璈停止執行的。故設定一個boolean的變數isStop,在while迴圈的時候,判斷是否開啟/關閉,在activity的onDestory方法中,設定其為true,即停止迴圈播放!