ViewPager實(shí)現(xiàn)輪播廣告圖
輪播廣告在現(xiàn)在的應(yīng)用中比較常見(jiàn),下面就來(lái)實(shí)現(xiàn)下該功能(文章參考了網(wǎng)上流傳的黑馬的視頻教程)先來(lái)看下具體的實(shí)現(xiàn)效果:
實(shí)現(xiàn)思路:
1.為ViewPager設(shè)置數(shù)據(jù)源,實(shí)現(xiàn)ViewPager的滾動(dòng)
2.將圓點(diǎn)指示器與ViewPager的頁(yè)面對(duì)應(yīng)起來(lái)
3.實(shí)現(xiàn)左右滑動(dòng)均能無(wú)限循環(huán)
4.實(shí)現(xiàn)自動(dòng)播放
5.實(shí)現(xiàn)當(dāng)手指滑動(dòng)的時(shí)候取消自動(dòng)播發(fā)
首先需要說(shuō)明的就是在這篇文章里并沒(méi)有同時(shí)實(shí)現(xiàn)左右循環(huán)滑動(dòng)和手指觸碰自動(dòng)停止的功能,這個(gè)問(wèn)題還沒(méi)有解決,留待以后再解決,也希望有知道的朋友在下面留言,先謝謝拉。
1.首先我們定義布局文件,用來(lái)顯示Banner
這里我們采用對(duì)的是相對(duì)布局RelativeLayout,當(dāng)然也可以采用幀布局FrameLayout。布局文件activity_mian.xml代碼:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/activity_main"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="com.qc.admin.mylunbotu.MainActivity">
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="160dp">
- <android.support.v4.view.ViewPager
- android:id="@+id/viewpager"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- </android.support.v4.view.ViewPager>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="40dp"
- android:layout_alignParentBottom="true"
- android:background="#66000000"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:padding="5dp">
- <TextView
- android:id="@+id/tv_desc"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:maxLines="1"
- android:text="這是圖片描述"
- android:textColor="@android:color/white" />
- <!--用來(lái)填充圓點(diǎn)指示器的容器-->
- <LinearLayout
- android:id="@+id/point_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="5dp"
- android:orientation="horizontal" />
- </LinearLayout>
- </RelativeLayout>
- </RelativeLayout>
2.初始化圓點(diǎn)指示器
這里我們提供的圖片數(shù)據(jù)和顯示的文本數(shù)據(jù)都是靜態(tài)的,直接從數(shù)組資源中獲得的,如:
- //圖片資源id數(shù)組
- imageResIds = new int[]{R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e};
- // 文本描述
- contentDescs = new String[]{
- "鞏俐不低俗,我就不能低俗",
- "撲樹(shù)又回來(lái)啦!再唱經(jīng)典老歌引萬(wàn)人大合唱",
- "揭秘北京電影如何升級(jí)",
- "樂(lè)視網(wǎng)TV版大派送",
- "熱血屌絲的反殺"
- };
我們可以自己定義兩個(gè)shape,來(lái)顯示圓點(diǎn)指示器的兩種不同顯示狀態(tài),然后再定義一個(gè)selector,將它設(shè)置為控件的背景(ImageView),通過(guò)其setEnable(Boolean bool)方法可以自動(dòng)的決定使用哪個(gè)圖片。代碼分別如下:
a.正常狀態(tài)下,灰色圓點(diǎn),point_normal.xml:
- <?xml version="1.0" encoding="utf-8"?>
- <shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
- <size
- android:width="5dp"
- android:height="5dp" />
- <solid android:color="#44000000" />
- </shape>
b.點(diǎn)擊狀態(tài)下,白色圓點(diǎn) point_pressed.xml:
- <?xml version="1.0" encoding="utf-8"?>
- <shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
- <size android:width="5dp" android:height="5dp"/>
- <solid android:color="#FFFFFFFF"/>
- </shape>
c.設(shè)置背景時(shí)的xml文件, point_bg.xml:
- <selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="true" android:drawable="@drawable/point_pressed"/>
- <item android:state_enabled="false" android:drawable="@drawable/point_normal"/>
- </selector>
具體代碼:
- private void initData() {
- //圖片資源id數(shù)組
- imageResIds = new int[]{R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e};
- // 文本描述
- contentDescs = new String[]{
- "鞏俐不低俗,我就不能低俗",
- "撲樹(shù)又回來(lái)啦!再唱經(jīng)典老歌引萬(wàn)人大合唱",
- "揭秘北京電影如何升級(jí)",
- "樂(lè)視網(wǎng)TV版大派送",
- "熱血屌絲的反殺"
- };
- imageViewList = new ArrayList<>();
- LinearLayout.LayoutParams mParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
- mParams.leftMargin = 15;
- mParams.topMargin = 2;
- ImageView imageView;
- ImageView pointView;
- //初始化要展示的ImageView,并添加圓點(diǎn)指示器
- for (int i = 0; i < imageResIds.length; i++) {
- // 初始化圖片
- imageView = new ImageView(this);
- imageView.setBackgroundResource(imageResIds[i]);
- imageViewList.add(imageView);
- // 初始化指示器
- pointView = new ImageView(this);
- pointView.setBackgroundResource(R.drawable.point_bg);
- pointView.setLayoutParams(mParams);
- if (i == 0) {
- textView.setText(contentDescs[0]);
- pointView.setEnabled(true);
- } else {
- pointView.setEnabled(false);
- }
- mPointsLayout.addView(pointView);
- }
- }
注意:創(chuàng)建shape文件的時(shí)候,如果找不到,可以切換到project目錄下,在drawable目錄下右鍵,new->Drawable resource file ,得到如下界面:
將selector直接改為shape即可
3.為ViewPager設(shè)置適配器
- class MyAdapter extends PagerAdapter {
- // 1.返回條目的總數(shù)
- @Override
- public int getCount() {
- //return imageViewList.size();
- return Integer.MAX_VALUE;
- }
- // 2.返回要顯示的條目,并創(chuàng)建條目
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- //container:容器,其實(shí)也就是ViewPager
- //position:當(dāng)前要顯示的條目的位置
- int newPosition = position % imageViewList.size();
- ImageView imageView = imageViewList.get(newPosition);
- //a.將View對(duì)象添加到container容器中
- container.addView(imageView);
- //b.把View對(duì)象返回給框架,適配器
- return imageView;
- }
- // 3.銷(xiāo)毀條目,其實(shí)就是將要銷(xiāo)毀的對(duì)象object從container中移除出去就好了
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- container.removeView((View) object);
- }
- // 4.指定復(fù)用的判斷邏輯(一般為固定寫(xiě)法)
- @Override
- public boolean isViewFromObject(View view, Object object) {
- // 當(dāng)滑動(dòng)到新的條目之后,又返回回來(lái),view是否可以被復(fù)用
- return view == object;
- }
- }
在這里我們實(shí)現(xiàn)的是偽無(wú)限循環(huán),其實(shí)就是將我們的第一項(xiàng)設(shè)置為一個(gè)很大的數(shù)的中間位置(Integer.MAX_VALUE),這樣當(dāng)我們向左向右滑動(dòng)的時(shí)候,將返回的position對(duì)數(shù)據(jù)的大小進(jìn)行取模運(yùn)算%,根據(jù)相應(yīng)的位置設(shè)置相應(yīng)的圖片或者文字即可。這樣就實(shí)現(xiàn)了向右向左都是無(wú)限循環(huán)了。
4.實(shí)現(xiàn)自動(dòng)播放
我們當(dāng)然可以直接開(kāi)啟一個(gè)線程,在里面設(shè)置ViewPager的當(dāng)前項(xiàng),但是直接使用Handler更便于進(jìn)行控機(jī)制,因?yàn)槲覀兛梢詾閂iewPager設(shè)置滾動(dòng)監(jiān)聽(tīng)器。自動(dòng)播放控制代碼:
- private void startRun() {
- mHandler = new Handler();
- mHandler.postDelayed(mTaskRunnable, delayMillis);
- }
- //該線程一直運(yùn)行著,知道activity被銷(xiāo)毀,此時(shí)將isActivityAlive設(shè)置為false
- final Runnable mTaskRunnable = new Runnable() {
- @Override
- public void run() {
- // 如果activity未被銷(xiāo)毀,就一直執(zhí)行該線程
- // 在ViewPager的OnPageChangeListener方法中決定是否將isAutoRun置反
- if (isActivityAlive) {
- if (isAutoRun) {
- viewPager.setCurrentItem((viewPager.getCurrentItem() + 1) % imageViewList.size());
- mHandler.postDelayed(mTaskRunnable, delayMillis);
- } else {
- mHandler.postDelayed(mTaskRunnable, delayMillis);
- }
- }
- }
- };
為ViewPager設(shè)置滾動(dòng)監(jiān)聽(tīng)器,這里我們直接讓當(dāng)前類(lèi)實(shí)現(xiàn)ViewPager.OnPageChangeListener接口,實(shí)現(xiàn)其中的抽象方法:
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- }
- @Override
- public void onPageSelected(int position) {
- int newPosition = position % imageViewList.size();
- textView.setText(contentDescs[newPosition]);
- // 先將上一個(gè)置位false,將當(dāng)前位置置位true,這樣可以使得初始化的時(shí)候就在第一個(gè)位置
- // (因?yàn)閜reviousSelectedItem的未賦值時(shí)候的初始值默認(rèn)為0)
- mPointsLayout.getChildAt(previousSelectedItem).setEnabled(false);
- mPointsLayout.getChildAt(newPosition).setEnabled(true);
- previousSelectedItem = newPosition;
- }
- @Override
- public void onPageScrollStateChanged(int state) {
- switch (state) {
- // 靜止?fàn)顟B(tài)
- case SCROLL_STATE_IDLE:
- isAutoRun = true;
- break;
- // 拖拽中
- case SCROLL_STATE_DRAGGING:
- isAutoRun = false;
- break;
- // 拖拽后松手,自動(dòng)回到最終位置的過(guò)程
- case SCROLL_STATE_SETTLING:
- isAutoRun = true;
- break;
- }
- }
這里面我們就實(shí)現(xiàn)了當(dāng)手指滑動(dòng)的時(shí)候停止自動(dòng)播放,當(dāng)手指抬起的時(shí)候又開(kāi)始了自動(dòng)播放。但是,注意,你以為這些代碼組合起來(lái)就能實(shí)現(xiàn)循環(huán)滾動(dòng)的同時(shí)還能手指控制是否自動(dòng)播放?好吧,我當(dāng)時(shí)也是這樣以為的,運(yùn)行后會(huì)報(bào)錯(cuò):
- java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
意思是我們待加入的子視圖已經(jīng)有了一個(gè)父視圖,需要調(diào)用它的父視圖的removeView()方法來(lái)將其移除,之后再添加,然而事實(shí)證明并沒(méi)有什么卵用。留待以后解決吧,在只能犧牲掉無(wú)線循環(huán)了,這里我們需要將getCount方法中的返回值改成我們數(shù)據(jù)的大小就好了:
- @Override
- public int getCount() {
- return imageViewList.size();
- // return Integer.MAX_VALUE;
- }
然后將之前設(shè)置的默認(rèn)的第一項(xiàng):
- private void initAdapter() {
- int firstPosition = Integer.MAX_VALUE / 2 - (Integer.MAX_VALUE / 2 % imageViewList.size());
- //viewPager.setOffscreenPageLimit(imageViewList.size());
- viewPager.setAdapter(new MyAdapter());
- // 設(shè)置從中間的某個(gè)位置開(kāi)始滑動(dòng),從而能夠?qū)崿F(xiàn)向左向右的循環(huán)滑動(dòng)
- viewPager.setCurrentItem(firstPosition);
- }
改為:
- private void initAdapter() {
- viewPager.setAdapter(new MyAdapter());
- // 設(shè)置從中間的某個(gè)位置開(kāi)始滑動(dòng),從而能夠?qū)崿F(xiàn)向左向右的循環(huán)滑動(dòng)
- viewPager.setCurrentItem(0);
- }
可能說(shuō)了這么多,看的都混了,在最后就貼一下完整的源碼吧(支持自動(dòng)播放、手指控制,但是不支持左右無(wú)限循環(huán))。
- package com.qc.admin.mylunbotu;
- import android.os.Bundle;
- import android.os.Handler;
- import android.support.v4.view.PagerAdapter;
- import android.support.v4.view.ViewPager;
- import android.support.v7.app.AppCompatActivity;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.ImageView;
- import android.widget.LinearLayout;
- import android.widget.TextView;
- import java.util.ArrayList;
- import java.util.List;
- import static android.support.v4.view.ViewPager.SCROLL_STATE_DRAGGING;
- import static android.support.v4.view.ViewPager.SCROLL_STATE_IDLE;
- import static android.support.v4.view.ViewPager.SCROLL_STATE_SETTLING;
- public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener {
- private ViewPager viewPager;
- private int[] imageResIds;
- private List<ImageView> imageViewList;
- private LinearLayout mPointsLayout;
- private String[] contentDescs;
- private int previousSelectedItem;
- private TextView textView;
- private Handler mHandler;
- boolean isAutoRun = true;
- boolean isActivityAlive = true;
- private int delayMillis = 2000;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- //初始化視圖
- initViews();
- //初始化數(shù)據(jù)
- initData();
- //初始化適配器
- initAdapter();
- //開(kāi)始自動(dòng)播放
- startRun();
- }
- private void startRun() {
- mHandler = new Handler();
- mHandler.postDelayed(mTaskRunnable, delayMillis);
- }
- //該線程一直運(yùn)行著,知道activity被銷(xiāo)毀,此時(shí)將isActivityAlive設(shè)置為false
- final Runnable mTaskRunnable = new Runnable() {
- @Override
- public void run() {
- // 如果activity未被銷(xiāo)毀,就一直執(zhí)行該線程
- // 在ViewPager的OnPageChangeListener方法中決定是否將isAutoRun置反
- if (isActivityAlive) {
- if (isAutoRun) {
- viewPager.setCurrentItem((viewPager.getCurrentItem() + 1) % imageViewList.size());
- mHandler.postDelayed(mTaskRunnable, delayMillis);
- } else {
- mHandler.postDelayed(mTaskRunnable, delayMillis);
- }
- }
- }
- };
- private void initAdapter() {
- //int firstPosition = Integer.MAX_VALUE / 2 - (Integer.MAX_VALUE / 2 % imageViewList.size());
- //viewPager.setOffscreenPageLimit(imageViewList.size());
- viewPager.setAdapter(new MyAdapter());
- // 設(shè)置從中間的某個(gè)位置開(kāi)始滑動(dòng),從而能夠?qū)崿F(xiàn)向左向右的循環(huán)滑動(dòng)
- //viewPager.setCurrentItem(firstPosition);
- viewPager.setCurrentItem(0);
- }
- private void initData() {
- //圖片資源id數(shù)組
- imageResIds = new int[]{R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e};
- // 文本描述
- contentDescs = new String[]{
- "鞏俐不低俗,我就不能低俗",
- "撲樹(shù)又回來(lái)啦!再唱經(jīng)典老歌引萬(wàn)人大合唱",
- "揭秘北京電影如何升級(jí)",
- "樂(lè)視網(wǎng)TV版大派送",
- "熱血屌絲的反殺"
- };
- imageViewList = new ArrayList<>();
- LinearLayout.LayoutParams mParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
- mParams.leftMargin = 15;
- mParams.topMargin = 2;
- ImageView imageView;
- ImageView pointView;
- //初始化要展示的ImageView,并添加圓點(diǎn)指示器
- for (int i = 0; i < imageResIds.length; i++) {
- // 初始化圖片
- imageView = new ImageView(this);
- imageView.setBackgroundResource(imageResIds[i]);
- imageViewList.add(imageView);
- // 初始化指示器
- pointView = new ImageView(this);
- pointView.setBackgroundResource(R.drawable.point_bg);
- pointView.setLayoutParams(mParams);
- if (i == 0) {
- textView.setText(contentDescs[0]);
- pointView.setEnabled(true);
- } else {
- pointView.setEnabled(false);
- }
- mPointsLayout.addView(pointView);
- }
- }
- private void initViews() {
- viewPager = (ViewPager) findViewById(R.id.viewpager);
- viewPager.addOnPageChangeListener(this);
- textView = (TextView) findViewById(R.id.tv_desc);
- // 用來(lái)添加圓點(diǎn)指示器
- mPointsLayout = (LinearLayout) findViewById(R.id.point_container);
- }
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- }
- @Override
- public void onPageSelected(int position) {
- int newPosition = position % imageViewList.size();
- textView.setText(contentDescs[newPosition]);
- // 先將上一個(gè)置位false,將當(dāng)前位置置位true,這樣可以使得初始化的時(shí)候就在第一個(gè)位置
- // (因?yàn)閜reviousSelectedItem的未賦值時(shí)候的初始值默認(rèn)為0)
- mPointsLayout.getChildAt(previousSelectedItem).setEnabled(false);
- mPointsLayout.getChildAt(newPosition).setEnabled(true);
- previousSelectedItem = newPosition;
- }
- @Override
- public void onPageScrollStateChanged(int state) {
- switch (state) {
- // 靜止?fàn)顟B(tài)
- case SCROLL_STATE_IDLE:
- isAutoRun = true;
- break;
- // 拖拽中
- case SCROLL_STATE_DRAGGING:
- isAutoRun = false;
- break;
- // 拖拽后松手,自動(dòng)回到最終位置的過(guò)程
- case SCROLL_STATE_SETTLING:
- isAutoRun = true;
- break;
- }
- }
- // 創(chuàng)建一個(gè)MyAdapter類(lèi),繼承自PagerAdapter來(lái)為ViewPager設(shè)置適配器
- class MyAdapter extends PagerAdapter {
- // 1.返回條目的總數(shù)
- @Override
- public int getCount() {
- return imageViewList.size();
- // return Integer.MAX_VALUE;
- }
- // 2.返回要顯示的條目,并創(chuàng)建條目
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- //container:容器,其實(shí)也就是ViewPager
- //position:當(dāng)前要顯示的條目的位置
- int newPosition = position % imageViewList.size();
- ImageView imageView = imageViewList.get(newPosition);
- //a.將View對(duì)象添加到container容器中
- container.addView(imageView);
- //b.把View對(duì)象返回給框架,適配器
- return imageView;
- }
- // 3.銷(xiāo)毀條目,其實(shí)就是將要銷(xiāo)毀的對(duì)象object從container中移除出去就好了
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- container.removeView((View) object);
- }
- // 4.指定復(fù)用的判斷邏輯(一般為固定寫(xiě)法)
- @Override
- public boolean isViewFromObject(View view, Object object) {
- // 當(dāng)滑動(dòng)到新的條目之后,又返回回來(lái),view是否可以被復(fù)用
- return view == object;
- }
- }
- @Override
- protected void onDestroy() {
- isActivityAlive = false;
- super.onDestroy();
- }
- }