1

轮播广告在现在的应用中比较常见,下面就来实现下该功能(文章参考了网上流传的黑马的视频教程)
先来看下具体的实现效果:

clipboard.png

实现思路:
1.为ViewPager设置数据源,实现ViewPager的滚动
2.将圆点指示器与ViewPager的页面对应起来
3.实现左右滑动均能无限循环
4.实现自动播放
5.实现当手指滑动的时候取消自动播发

首先需要说明的就是在这篇文章里并没有同时实现左右循环滑动和手指触碰自动停止的功能,这个问题还没有解决,留待以后再解决,也希望有知道的朋友在下面留言,先谢谢拉。

1.首先我们定义布局文件,用来显示Banner

这里我们采用对的是相对布局RelativeLayout,当然也可以采用帧布局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" />

            <!--用来填充圆点指示器的容器-->
            <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.初始化圆点指示器

这里我们提供的图片数据和显示的文本数据都是静态的,直接从数组资源中获得的,如:

        //图片资源id数组
        imageResIds = new int[]{R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e};
        // 文本描述
        contentDescs = new String[]{
                "巩俐不低俗,我就不能低俗",
                "扑树又回来啦!再唱经典老歌引万人大合唱",
                "揭秘北京电影如何升级",
                "乐视网TV版大派送",
                "热血屌丝的反杀"
        };

我们可以自己定义两个shape,来显示圆点指示器的两种不同显示状态,然后再定义一个selector,将它设置为控件的背景(ImageView),通过其setEnable(Boolean bool)方法可以自动的决定使用哪个图片。代码分别如下:

a.正常状态下,灰色圆点,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.点击状态下,白色圆点 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.设置背景时的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数组
        imageResIds = new int[]{R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e};
        // 文本描述
        contentDescs = new String[]{
                "巩俐不低俗,我就不能低俗",
                "扑树又回来啦!再唱经典老歌引万人大合唱",
                "揭秘北京电影如何升级",
                "乐视网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,并添加圆点指示器
        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);
        }
    }

注意:创建shape文件的时候,如果找不到,可以切换到project目录下,在drawable目录下右键,new->Drawable resource file ,得到如下界面:

clipboard.png

将selector直接改为shape即可

3.为ViewPager设置适配器

 class MyAdapter extends PagerAdapter {

        // 1.返回条目的总数
        @Override
        public int getCount() {

            //return imageViewList.size();
            return Integer.MAX_VALUE;
        }

        // 2.返回要显示的条目,并创建条目
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            //container:容器,其实也就是ViewPager
            //position:当前要显示的条目的位置

            int newPosition = position % imageViewList.size();
            ImageView imageView = imageViewList.get(newPosition);
            //a.将View对象添加到container容器中
            container.addView(imageView);
            //b.把View对象返回给框架,适配器
            return imageView;
        }

        // 3.销毁条目,其实就是将要销毁的对象object从container中移除出去就好了
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }

        // 4.指定复用的判断逻辑(一般为固定写法)
        @Override
        public boolean isViewFromObject(View view, Object object) {
            // 当滑动到新的条目之后,又返回回来,view是否可以被复用
            return view == object;
        }
    }

在这里我们实现的是伪无限循环,其实就是将我们的第一项设置为一个很大的数的中间位置(Integer.MAX_VALUE),这样当我们向左向右滑动的时候,将返回的position对数据的大小进行取模运算%,根据相应的位置设置相应的图片或者文字即可。这样就实现了向右向左都是无限循环了。

4.实现自动播放

我们当然可以直接开启一个线程,在里面设置ViewPager的当前项,但是直接使用Handler更便于进行控机制,因为我们可以为ViewPager设置滚动监听器。
自动播放控制代码:

   private void startRun() {
        mHandler = new Handler();
        mHandler.postDelayed(mTaskRunnable, delayMillis);

    }

    //该线程一直运行着,知道activity被销毁,此时将isActivityAlive设置为false
    final Runnable mTaskRunnable = new Runnable() {
        @Override
        public void run() {
            // 如果activity未被销毁,就一直执行该线程
            // 在ViewPager的OnPageChangeListener方法中决定是否将isAutoRun置反
            if (isActivityAlive) {
                if (isAutoRun) {
                    viewPager.setCurrentItem((viewPager.getCurrentItem() + 1) % imageViewList.size());
                    mHandler.postDelayed(mTaskRunnable, delayMillis);
                } else {
                    mHandler.postDelayed(mTaskRunnable, delayMillis);
                }
            }
        }
    };

为ViewPager设置滚动监听器,这里我们直接让当前类实现ViewPager.OnPageChangeListener接口,实现其中的抽象方法:

    @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]);

        // 先将上一个置位false,将当前位置置位true,这样可以使得初始化的时候就在第一个位置
        // (因为previousSelectedItem的未赋值时候的初始值默认为0)
        mPointsLayout.getChildAt(previousSelectedItem).setEnabled(false);
        mPointsLayout.getChildAt(newPosition).setEnabled(true);
        previousSelectedItem = newPosition;
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        switch (state) {
            // 静止状态
            case SCROLL_STATE_IDLE:
                isAutoRun = true;
                break;
            // 拖拽中
            case SCROLL_STATE_DRAGGING:
                isAutoRun = false;
                break;
            // 拖拽后松手,自动回到最终位置的过程
            case SCROLL_STATE_SETTLING:
                isAutoRun = true;
                break;
        }
    }

这里面我们就实现了当手指滑动的时候停止自动播放,当手指抬起的时候又开始了自动播放。但是,注意,你以为这些代码组合起来就能实现循环滚动的同时还能手指控制是否自动播放?好吧,我当时也是这样以为的,运行后会报错:

java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

clipboard.png
意思是我们待加入的子视图已经有了一个父视图,需要调用它的父视图的removeView()方法来将其移除,之后再添加,然而事实证明并没有什么卵用。留待以后解决吧,在只能牺牲掉无线循环了,这里我们需要将getCount方法中的返回值改成我们数据的大小就好了:

        @Override
        public int getCount() {

            return imageViewList.size();
           // return Integer.MAX_VALUE;
        }

然后将之前设置的默认的第一项:

    private void initAdapter() {
        int firstPosition = Integer.MAX_VALUE / 2 - (Integer.MAX_VALUE / 2 % imageViewList.size());
        //viewPager.setOffscreenPageLimit(imageViewList.size());
        viewPager.setAdapter(new MyAdapter());
        // 设置从中间的某个位置开始滑动,从而能够实现向左向右的循环滑动
        viewPager.setCurrentItem(firstPosition);
    }

改为:

    private void initAdapter() {

        viewPager.setAdapter(new MyAdapter());
        // 设置从中间的某个位置开始滑动,从而能够实现向左向右的循环滑动
        viewPager.setCurrentItem(0);
    }

可能说了这么多,看的都混了,在最后就贴一下完整的源码吧(支持自动播放、手指控制,但是不支持左右无限循环)。

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();

        //初始化数据
        initData();

        //初始化适配器
        initAdapter();

        //开始自动播放
        startRun();

    }

    private void startRun() {
        mHandler = new Handler();
        mHandler.postDelayed(mTaskRunnable, delayMillis);

    }

    //该线程一直运行着,知道activity被销毁,此时将isActivityAlive设置为false
    final Runnable mTaskRunnable = new Runnable() {
        @Override
        public void run() {
            // 如果activity未被销毁,就一直执行该线程
            // 在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());
        // 设置从中间的某个位置开始滑动,从而能够实现向左向右的循环滑动
        //viewPager.setCurrentItem(firstPosition);
        viewPager.setCurrentItem(0);
    }

    private void initData() {

        //图片资源id数组
        imageResIds = new int[]{R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e};
        // 文本描述
        contentDescs = new String[]{
                "巩俐不低俗,我就不能低俗",
                "扑树又回来啦!再唱经典老歌引万人大合唱",
                "揭秘北京电影如何升级",
                "乐视网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,并添加圆点指示器
        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);
        // 用来添加圆点指示器
        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]);

        // 先将上一个置位false,将当前位置置位true,这样可以使得初始化的时候就在第一个位置
        // (因为previousSelectedItem的未赋值时候的初始值默认为0)
        mPointsLayout.getChildAt(previousSelectedItem).setEnabled(false);
        mPointsLayout.getChildAt(newPosition).setEnabled(true);
        previousSelectedItem = newPosition;
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        switch (state) {
            // 静止状态
            case SCROLL_STATE_IDLE:
                isAutoRun = true;
                break;
            // 拖拽中
            case SCROLL_STATE_DRAGGING:
                isAutoRun = false;
                break;
            // 拖拽后松手,自动回到最终位置的过程
            case SCROLL_STATE_SETTLING:
                isAutoRun = true;
                break;
        }
    }

    // 创建一个MyAdapter类,继承自PagerAdapter来为ViewPager设置适配器
    class MyAdapter extends PagerAdapter {

        // 1.返回条目的总数
        @Override
        public int getCount() {

            return imageViewList.size();
//            return Integer.MAX_VALUE;
        }

        // 2.返回要显示的条目,并创建条目
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            //container:容器,其实也就是ViewPager
            //position:当前要显示的条目的位置

            int newPosition = position % imageViewList.size();
            ImageView imageView = imageViewList.get(newPosition);
            //a.将View对象添加到container容器中
            container.addView(imageView);
            //b.把View对象返回给框架,适配器
            return imageView;
        }

        // 3.销毁条目,其实就是将要销毁的对象object从container中移除出去就好了
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }

        // 4.指定复用的判断逻辑(一般为固定写法)
        @Override
        public boolean isViewFromObject(View view, Object object) {
            // 当滑动到新的条目之后,又返回回来,view是否可以被复用
            return view == object;
        }
    }

    @Override
    protected void onDestroy() {
        isActivityAlive = false;
        super.onDestroy();
    }
}


warmcheng
153 声望14 粉丝