自定义View之无限大图轮播ShufBanner

无限大图轮播–ShufBanner

轮播图作为一个app的宣传,展示等,往往占据着一个很重要的地位,大部分app都将其放在首页。那么通常的做法都是使用ViewPager,使其能够作用滑动,而无限轮播无外乎两种做法。

- 第一种是将ViewPager的size定义为无限大,定义其初始显示的位置为中间,这样的话因为左或者右都有很多的页面,所以造成了一种可以无限轮播的假象。同时因为ViewPager的特性,其只是加载当前显示page以及左和右的三个页面,不用担心OOM。

- 第二种是,将ViewPager的最前和最后的页面复制一份之后,分别加入到最后和最前,当ViewPager滑动到最前,或者最后的位置,直接跳转到相对应的位置。即可。

本次自定义的ShufBanner使用的是第二种方式。

首先看图

他的使用方法很简单,如下即可在xml文件中添加控件

  <mahao.alex.shuffingbanner.ShufBanner
        android:id="@+id/shuf"
        android:layout_width="match_parent"
        android:layout_height="300dp"/>

public class MainActivity extends AppCompatActivity  {

    private ShufBanner mShufBanner;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        List<String> mImages = new ArrayList<>();
        mImages.add("http://pic.huodongjia.com/event/2015-11-18/event151612.jpg");
        mImages.add("http://pic.huodongjia.com/event/2016-03-17/event177131.jpg");
        mImages.add("http://pic.huodongjia.com/event/2015-12-03/event156106.jpg");

        mShufBanner = ((ShufBanner) findViewById(R.id.shuf));

        //启动轮播图
        mShufBanner.startShuf(mImages,true);

        //设置监听
        mShufBanner.setItemClcikListener(new ShufBannerClickListener() {
            @Override
            public void onClick(int position) {
                Toast.makeText(MainActivity.this, ""+position, Toast.LENGTH_SHORT).show();
            }
        });
    }

}

只需要启动轮播,设置监听即可。下面我们就开始封装。

  • 首先考虑布局,布局应该是一个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="300dp"
    android:orientation="vertical"
    >

    <android.support.v4.view.ViewPager
        android:id="@+id/vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <RelativeLayout
        android:layout_alignParentBottom="true"
        android:background="#6fff"
        android:layout_width="match_parent"
        android:layout_height="20dp">

        <LinearLayout
            android:id="@+id/ll_navigation"
            android:layout_centerInParent="true"
            android:orientation="horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    </RelativeLayout>

</RelativeLayout>

ViewPager没有什么疑问,而之所以在这里放置一个LinearLayout,是因为我们并不确定有多少页,所以我们需要根据实际情况在代码里添加。

  • 创建类shufBanner继承RelativeLayout,加载布局文件,初始化ViewPager,查找控件等等。

    public ShufBanner(Context context, AttributeSet attrs) {
        super(context, attrs);

        inflate(getContext(), R.layout.widget_shufbanner, this);

        initImageLoader();

        initViewPager();

    }

    /**
     * 初始化ViewPager
     */
    private void initViewPager() {
        mVp = ((ViewPager) findViewById(R.id.vp));
        //图片url地址
        mImages = new ArrayList<>();
        //imageView对象
        mImageViews = new ArrayList<>();

        mAdapter = new ShufBannerAdapter(getContext(), mImageViews);

        mVp.setAdapter(mAdapter);

        mVp.addOnPageChangeListener(this);

        mNavigationLayout = (LinearLayout) findViewById(R.id.ll_navigation);

    }

在这里我们首先将布局文件加载到ShufBanner中,其次初始化ViewPager,并设置其Adapter,添加滚动监听,监听事件的逻辑后面再说。同时查找到我们mNavigationLayout(导航点的父控件),我们看一下ShufBannerAdapter的代码

public class ShufBannerAdapter extends PagerAdapter {

    private Context context;
    private List<ImageView> mImageViews;

    public ShufBannerAdapter(Context context, List<ImageView> mImages) {
        this.context = context;
        this.mImageViews = mImages;

    }

    @Override
    public int getCount() {
        return mImageViews==null?0:mImageViews.size();
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        container.addView(mImageViews.get(position));

        return mImageViews.get(position);
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView(mImageViews.get(position));
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }
}

ShufBannerAdapter很简单,只是一个最基本的适配器,没有在里面处理什么逻辑。

基本的东西已经添加完,下面就是开始实现具体的逻辑,即完成startShuf()方法的逻辑。

 public void startShuf(List<String> urls, boolean isStartShuf) {

        if (urls.size() == 0 || urls == null) {
            return;
        }
        /**
         * 清楚数据
         */
        clearAllData();

        mImages.addAll(urls);
        /**
         * 保存图片地址
         */
        saveUrl();

        /**
         * 根据图片url创建imgView
         */
        createImageView();

        /**
         * 生成导航点
         */
        addPoint2Navigation();

        //开始刷新
        mAdapter.notifyDataSetChanged();
        mVp.setCurrentItem(1);

        //发送循环请求
        this.isStartShuf = isStartShuf;
        if (this.isStartShuf&&mImages.size()>3) {
            handler.sendEmptyMessageAtTime(1, 2000);
        }
    }

首先判断为null之类的都懂。下面清除数据,该方法主要目的是,当我们刷新数据时,我们必须把之前老的数据清楚,同时一些属性置空。

mImages.addAll(urls);将我们的图片集合添加到当前集合中,因为我们需要实现无限循环,即把最前和最后添加一张图片,所以我们通过saveUrl()方法添加数据。


    private void saveUrl() {

        String startImageUrl = mImages.get(0);
        String endImageUrl = mImages.get(mImages.size() - 1);

        mImages.add(0, endImageUrl);
        mImages.add(mImages.size(), startImageUrl);
    }

我们获取到我们图片url集合的第一张和最后一张,将第一张加到List末尾,将最后一张加到list的最前端。就好比,我们本来有三个url:1,2,3。经过saveUrl方法之后,url:_3,1,2,3,_1。

createImageView()方法根据url创建对应的imageView对象并存储到list集合中。

addPoint2Navigation()方法是根据当前的ImageView添加导航点,我们看一下他的详细实现过程:

 /**
     * 添加导航点到布局中
     */
    private void addPoint2Navigation() {
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT
        );
        int pointPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics());
        params.setMargins(pointPadding, 0, pointPadding, 0);
        for (int i = 0; i < mImageViews.size() - 2; i++) {
            ImageView point = new ImageView(getContext());
            point.setLayoutParams(params);
            point.setImageResource(R.drawable.select_navgation_point);
            if (i == 0) {
                point.setEnabled(true);
            } else {
                point.setEnabled(false);
            }
            mNavigationLayout.addView(point);
        }
    }

我们添加导航点的时候需要注意,因为我们添加了两张图片,所以我们在添加的实际导航点数 = 图片数-2;导航点使用的shape图形资源画出的,同时导航点因为有选中和不选中的两种状态,通过定义selector改变颜色变化,因为使用的是enabled属性来判断,所以在这里设置Enable属性。

 //发送循环请求
        this.isStartShuf = isStartShuf;
        if (this.isStartShuf&&mImages.size()>3) {
            handler.sendEmptyMessageAtTime(1, 2000);
        }

ok 下面开始进入到了轮播的逻辑了。

我们发送一个message给handler。

private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {

            int showPonit = mVp.getCurrentItem();

            showPonit = showPonit + 1;

            //getChildCount()获取的是当前存在的子类,销毁机制

           /* if (showPonit >= mImageViews.size()) {

                showPonit = 1;
            }*/

            //获得的是正常位置
            mVp.setCurrentItem(showPonit);

            if (isStartShuf) {
                sendEmptyMessageDelayed(1, 2000);
            }
        }
    };

在handler中,我们获取到当前的的页面,并+1。

判断是否超过了最大页面,如果超过了最大页面,就置为1,即我们的第一页,但后来又被我注掉,因为,我发现根本不需要。有点想多了。我们先继续往下看,设置页面,判断是否轮播,继续发送message。

到这里,我们的页面开始滚动了,但我们还需要处理逻辑,因为我们的页面现在是_3,1,2,3,_1.现在我们要加入判断,我们利用setCurrentItem(int,boolean)方法,到页面_1时,跳转到1,当页面在_3时,我们默认跳转到3。很明显,添加mVp.addOnPageChangeListener(this);

该方法有三个回调,分别是onPageScrolled:页面滚动距离,onPageSelected:页面选择,onPageScrollStateChanged:页面滚动状态的选择。

在这里我选择在onPageScrolled方法

  @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

        //是否滚动过渡完结
        if (positionOffset == 0) {
            if (position == 0) {
                mVp.setCurrentItem(mImageViews.size() - 2, false);
            }

            if (position == mImageViews.size() - 1) {
                mVp.setCurrentItem(1, false);
            }
        }
    }

最初,我是在onPageSelected(int position)方法中,添加跳转逻辑,但有一个问题。当我们调用

setCurrentItem()方法时,会立即回掉onPageSelected,但此时我们页面还是在滚动过渡中,如果此时3想_1滚动,但滚动到一半时,有调用了跳转到1的方法,切该方法无过渡,导致页面闪烁。所以我们在onPageScrolled判断,当滚动完毕,我们开始进行对应跳转。

在之前handler中,我添加了一个边界的判断,但在这里会发现,我们有了这个过渡跳转,所以不会存在边界。

页面已经滚动,下面开始就是要将页面的滚动和导航点联动,这个在onPageSelected中即可

    @Override
    public void onPageSelected(int position) {
        /**
         * 修改当前点击点的位置
         */
        changePosition(position);

        /**
         * 修改点的状态
         */
        changePointState(position);
    }

changePointState(position);为修改点的状态。

**
     * 改变导航点的状态
     *
     * @param position
     */
    private void changePointState(int position) {

        if (position == 0) {
            position = mImageViews.size() - 2;
        }
        if (position == mImageViews.size() - 1) {
            position = 1;
        }

        position = position - 1;

        for (int i = 0; i < mNavigationLayout.getChildCount(); i++) {
            if (i == position) {
                mNavigationLayout.getChildAt(i).setEnabled(true);

            } else {
                mNavigationLayout.getChildAt(i).setEnabled(false);
            }
        }
    }

这个就是判断一下是否是_3和_1,改变position对应的3和1,最后遍历修改状态。

changePosition(position);这个方法,是干什么呢。这就涉及到下一个关键,及ShufBanner的点击事件回调。

对于轮播图,往往都会有详情页,及跳转链接。如果我们对于每个ImageView都加入监听,这无疑很麻烦,我们还要判断具体的点击,以及_3,_1的问题。所以在这里我定义了一个字段

    /**
     * 点击事件的位置
     */
    private int position = -1;

该字段,用来保存当前ViewPager的position。默认等于-1。

   /**
     * 改变当前点击的点
     *
     * @param position
     */
    private void changePosition(int position) {
        if (position == 0) {
            position = mImageViews.size() - 2;
        }
        if (position == mImageViews.size() - 1) {
            position = 1;
        }

        position--;
        this.position = position;
    }

因为我需要接口回调

public interface ShufBannerClickListener {
    /**
     *
     * @param position -1 未设置任何点击
     */
    void onClick(int position);
}

我们如果直接记录position,我们自己内部悄悄添加了两个页面。这样,对于调用者来说,我传入的数据和返回的角标数据无法一一对应,这样肯定是不对。所以在这里改变点的状态。

这样应该差不多了,我们看一下调用方式


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        List<String> mImages = new ArrayList<>();
        mImages.add("http://pic.huodongjia.com/event/2015-11-18/event151612.jpg");
        mImages.add("http://pic.huodongjia.com/event/2016-03-17/event177131.jpg");
        mImages.add("http://pic.huodongjia.com/event/2015-12-03/event156106.jpg");

        mShufBanner = ((ShufBanner) findViewById(R.id.shuf));

        //启动轮播图
        mShufBanner.startShuf(mImages,true);

        //设置监听
        mShufBanner.setItemClcikListener(new ShufBannerClickListener() {
            @Override
            public void onClick(int position) {
                Toast.makeText(MainActivity.this, ""+position, Toast.LENGTH_SHORT).show();
            }
        });
    } 

Over。

总结:

1. 大图轮播实现的原理:1,2,3三个页面,我们最后和之前各添加一个页面,_3,1,2,3,_1,然后实现逻辑当_3页面时跳转到3,_1跳转1.

2. ViewPager的getCount()方法,ViewPager的自带销毁机制,所以,和我们想要获取的值会有出入。

该项目于已发布与github,有意者请移步ShuffingBanner

时间: 2024-10-22 08:37:40

自定义View之无限大图轮播ShufBanner的相关文章

Android无限广告轮播 - 自定义BannerView

1.概述 这其实是我第一篇想写的博客,可能是因为我遇到了太多的坑,那个时候刚入行下了很多Demo发现怎么也改不动,可能是能力有限,这次就做一个具体的实现和彻底的封装. 上次讲了Android无限广告轮播-ViewPager源码分析,有了源码分析我们对ViewPager就有了一个大概的了解,那么再来封装成自定义View,就会简单许多,附视频讲解地址:http://pan.baidu.com/s/1bpqqkGn 2.效果封装 2.1 自定义BannerViewPager extends ViewP

大图轮播

之前有整理过用js做的大图轮播,相对来说看起来比较繁琐,今天来写一些用bootstrap做的大图轮播,看起来非常简单: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Bootstrap 实例 - 简单的轮播(Carousel)插件</title> <link rel="stylesheet" href="h

UICollectionView实现无限图片轮播

目的:无限图片轮播 技术:UICollectionView 技术要点:利用UICollectionViewCell的重用实现无限图片轮播,实例提供5张图片 主控制器代码 //  ViewController.m //  UICollectionView-无限轮播-demo #import "ViewController.h" #import "BQAdvCell.h" #import "BQBeauty.h" @interface ViewCon

JS对于导航栏、下拉菜单以及选项卡的切换操作、大图轮播(主要练习对于样式表的操作)

1.导航拦以及下拉菜单 css样式表代码 .div1 { width: 100px; height: 30px; background-color: red; float: left; margin-right: 10px; } .div1-div { width: 100%; height: 400px; background-color: green; margin-top: 30px; display: none; } JS脚本代码 <!DOCTYPE html> <html xm

bootstrap大图轮播手机端不能手指滑动解决办法

网上看了很多解决办法,几乎本质都是一样的,都是引入一个滑动的js插件,加入一段js代码,即可生效,但是我试了hammer.js 和 touchSwipe.js 都不生效,也找不到原因是什么,目前在网上就找到 toucher.js 可以实现,贴到博客,留作备用:  1 <script>   2     $(function(){   3         var myTouch = util.toucher(document.getElementById('carousel-index')); 

简单易懂实用的大图轮播

<head> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>大图轮播</title> <style type="text/css"> * { margin: 0px; padding: 0px; } #container { width: 500px; h

[DBW]大图轮播,可通过两种方法实现

通过在div中加入表格,实现大图轮播,代码如下: 整体的思路: 1.在div中嵌入表格,设置div的宽和高,设置成图片大小,确定其位置,将图片插入表格,超出div部分隐藏 2.在js中定义一个变量接受left的值,并赋值为0px,即初始值 3设置函数 function  a() {在函数中将接收到的值强制转化为数字, 然后在值中减掉一张图片的宽度, } 3.延迟执行,设置需要延迟的代码及延迟的时间,最后回到表格中,加入一个调用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14

例题:大图轮播

大图轮播 图片轮播 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv=&quo

利用jQuery实现图片无限循环轮播(不借助于轮播插件)

原来我主要是用Bootstrap来实现轮播图的功能,而这次是用javaScript和jQuery来实现图片无限循环轮播! 用到的技术有:html.css.JavaScript(少).jQuery(主要) 效果展示: html代码: <body> <div id="container"><!-- left:-600px 表示:页面加载出现的第一张图片是1.jp --> <div id="list" style="le