Android無限廣播輪播

MyCustomBannerView

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
dependencies {
    compile 'com.github.Thor-jelly: MyCustomBannerView:最新版本號'
}

Android無限廣播輪播

使用了類似view復(fù)用的方法

if (reuseView == null) {
    iv = new ImageView(MainActivity.this);
} else {
    iv = (ImageView) reuseView;
    Log.d(TAG, "getView: 界面復(fù)用View"+reuseView);
}

分析實(shí)現(xiàn)方式

  • 系統(tǒng)ViewPager + 自定義 extends ViewPagers
  • 自定義ViewGroup + extends HorizontalScrollView

參數(shù)的傳遞

  • 傳遞圖片鏈接數(shù)組
  • Adapter適配器模式

ViewPager源碼分析

參照Android無限廣告輪播 - ViewPager源碼分析

實(shí)現(xiàn)

  1. 自定義View BannerViewPager

    • 創(chuàng)建自定義View Pager
    /**
     * 類描述:自定義Banner <br/>
     * 創(chuàng)建人:吳冬冬<br/>
     * 創(chuàng)建時間:2018/3/1 14:50 <br/>
     */
    public class BannerViewPager extends ViewPager {
        /**
         * 自定義BannerView柄粹,默認(rèn)的自定義的adapter
         */
        private BannerAdapter mBannerAdapter;
    
        public BannerViewPager(Context context) {
            super(context);
        }
    
        public BannerViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public void setAdapter(BannerAdapter adapter) {
            mBannerAdapter = adapter;
            //設(shè)置父類ViewPager的adapter
            setAdapter(new BannerPagerAdapter());
        }
    
        /**
         * 給ViewPager設(shè)置適配器
         */
        private class BannerPagerAdapter extends PagerAdapter {
            @Override
            public int getCount() {
                //為了實(shí)現(xiàn)無限循環(huán)
                return Integer.MAX_VALUE;
            }
    
            @Override
            public boolean isViewFromObject(View view, Object object) {
                //官方推薦這么寫疼燥,源碼中寫了
                return view == object;
            }
    
            //創(chuàng)建viewpager 條目回調(diào)方法
            @Override
            public Object instantiateItem(ViewGroup container, int position) {
                /*
                    如果這里寫死,比如直接ImageView imageView = new ImageView();
                    所以這里要使用adapter設(shè)置模式,為了讓我們自定義
                 */
                View bannerItemView = mBannerAdapter.getView(position);
                //添加自定義的View樣式
                container.addView(bannerItemView);
                return bannerItemView;
            }
    
            //銷毀ViewPager 條目回調(diào)方法
            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
                /*
                    下面為什么注釋呢笋鄙,可以直接點(diǎn)進(jìn)去看一下源碼杯拐,只是拋出一個異常沒有做其他處理
                    throw new UnsupportedOperationException("Required method destroyItem was not overridden");
                 */
                //super.destroyItem(container, position, object);
                container.removeView((View) object);
                object = null;//釋放一下內(nèi)存
            }
        }
    }
    
    • 創(chuàng)建適配器
    /**
     * 類描述:<br/>
     * 創(chuàng)建人:吳冬冬<br/>
     * 創(chuàng)建時間:2018/3/1 15:04 <br/>
     */
    public abstract class BannerAdapter {
        /**
         * 根據(jù)位置獲取ViewPager的子View
         */
        public abstract View getView(int position);
    }
    
    • 在布局中加入自定義ViewPager,并設(shè)置適配器
    final List<String> ss = new ArrayList<>();
        ss.add("http://bbs.everychina.com/data/attachment/forum/201312/04/102637cwnwrywccfxwab22.jpg");
        ss.add("http://img3.100bt.com/upload/ttq/20131121/1385034610130_middle.jpg");
        ss.add("http://pic25.nipic.com/20121123/9830190_172507668164_2.jpg");
        ss.add("http://img5q.duitang.com/uploads/item/201504/24/20150424H4855_LfPvj.jpeg");
        ss.add("http://bcs.91.com/rbpiczy/Wallpaper/2014/11/17/91496cd61ea94d1598345b94c6d246f7-9.jpg");
        mBannerViewPager.setAdapter(new BannerAdapter() {
            @Override
            public View getView(int position) {
                Log.d(TAG, "getView: "+position);
                int p = position;
                if (p >= ss.size()) {
                    p = ss.size() - 1;
                }
                ImageView iv = new ImageView(MainActivity.this);
                Glide.with(MainActivity.this)
                        .load(ss.get(p))
                        .apply(new RequestOptions().placeholder(R.mipmap.ic_launcher))
                        .into(iv);
                return iv;
            }
        });
    
  2. 實(shí)現(xiàn)無限循環(huán)效果
    實(shí)現(xiàn)自動輪播的方式

    • Timer 寫一個定時器
    • Handler發(fā)送消息(Handle可能出現(xiàn)內(nèi)存泄漏問題血巍,activity生命周期沒有handle生命周期大)
    • start Thread() 開一個子線程

    我們這里采用Handle的方式:

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //每隔多少秒后切換到下一頁
            setCurrentItem(getCurrentItem() + 1);
            //不斷執(zhí)行
            startRoll();
        }
    };
    
    /**
     * 實(shí)現(xiàn)自動輪播
     */
    public void startRoll() {
        //清除消息萧锉,防止多次發(fā)送
        mHandler.removeMessages(SCROLL_MSG);
    
        //發(fā)送延遲空消息,實(shí)現(xiàn)自動輪播
        mHandler.sendEmptyMessageDelayed(SCROLL_MSG, mScrollNextTime);
    }
    
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        //銷毀Handle 停止發(fā)送述寡,解決內(nèi)存泄漏
        mHandler.removeMessages(SCROLL_MSG);
        mHandler = null;
    }
    
  3. 改變切換的速率
    在setCurrentItem源碼中我們可以看到下面一段代碼

    private Scroller mScroller;
    mScroller.startScroll(sx, sy, dx, dy, duration);
    

    而改變速率只有這一個方式柿隙,因此我們需要改變duration的時間但是duration是一個局部變量無法改變玫恳,因此我們只能改變mScroller(通過反射方法)。

    • 自定義Scroller方法
    /**
     * 類描述:<br/>
     * 創(chuàng)建人:吳冬冬<br/>
     * 創(chuàng)建時間:2018/3/1 17:04 <br/>
     */
    public class BannerScroller extends Scroller {
        /**
         * 動畫持續(xù)時間
         */
        private int mScrollDuration = 850;
        /**
         * 設(shè)置頁面切換動畫持續(xù)時間
         */
        public void setScrollDuration(int scrollDuration) {
            mScrol  lDuration = scrollDuration;
        }
    
        public BannerScroller(Context context) {
            this(context, null);
        }
    
        public BannerScroller(Context context, Interpolator interpolator) {
            this(context, interpolator, context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
        }
    
        public BannerScroller(Context context, Interpolator interpolator, boolean flywheel) {
            super(context, interpolator, flywheel);
        }
    
        @Override
        public void startScroll(int startX, int startY, int dx, int dy, int duration) {
            super.startScroll(startX, startY, dx, dy, mScrollDuration);
        }
    }
    
    • 添加反射獲取到mScroller并重新賦值成我們自定義的Scroller方法

    在構(gòu)造方法下添加下面代碼:

    try {
            //改變ViewPager的速率
            Field mScroller = ViewPager.class.getDeclaredField("mScroller");
            //設(shè)置參數(shù)
            mScroller.setAccessible(true);
            //第二個參數(shù)為插值器优俘,需要的可以添加第二個參數(shù)
            mBannerScroller = new BannerScroller(context);
            mScroller.set(this, mBannerScroller);
        } catch (Exception e) {
            e.printStackTrace();
        }
    

    設(shè)置持續(xù)時間

    /**
     * 設(shè)置頁面切換動畫持續(xù)時間
     */
    public void setScrollDuration(int scrollDuration) {
        mBannerScroller.setScrollDuration(scrollDuration);
    }
    
  1. 自定義BannerView

    構(gòu)建自定義BannerView里面包含:輪播圖京办、當(dāng)前輪播圖描述、輪播圖點(diǎn)

    /**
     * 類描述:自定義banner <br/>
     * 創(chuàng)建人:吳冬冬<br/>
     * 創(chuàng)建時間:2018/3/2 10:45 <br/>
     */
    public class BannerView extends RelativeLayout{
    
        /**
         * 輪播圖
         */
        private BannerViewPager mBannerVp;
        /**
         * 當(dāng)前圖描述
         */
        private TextView mBannerDescribeTv;
        /**
         * 點(diǎn)布局
         */
        private LinearLayout mBannerDotLl;
    
        /**
         * 自定義的bannerAdapter
         */
        private BannerAdapter mBannerAdapter;
    
        public BannerView(Context context) {
            this(context, null);
        }
    
        public BannerView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            //加載布局進(jìn)當(dāng)前view
            View view = inflate(context, R.layout.banner_layout, this);
            //初始化View
            initView(view);
    
        }
    
        /**
         * 初始化View
         */
        private void initView(View view) {
            mBannerVp = (BannerViewPager) view.findViewById(R.id.banner_vp);
            mBannerDescribeTv = (TextView) view.findViewById(R.id.banner_describe_tv);
            mBannerDotLl = (LinearLayout) view.findViewById(R.id.banner_dot_ll);
        }
    
        /**
         * 設(shè)置適配器
         */
        public void setAdapter(BannerAdapter adapter) {
            mBannerAdapter = adapter;
            mBannerVp.setAdapter(adapter);
        }
    
        /**
         * 實(shí)現(xiàn)自動輪播
         */
        public void startRoll() {
            mBannerVp.startRoll();
        }
    
        /**
         * 設(shè)置頁面切換動畫持續(xù)時間
         */
        public void setScrollDuration(int scrollDuration) {
            mBannerVp.setScrollDuration(scrollDuration);
        }
    }
    
```
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!--滾動條-->
    <com.dongdongwu.test.banner.BannerViewPager
        android:id="@+id/banner_vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />


    <!--廣告描述和小點(diǎn)布局-->
    <RelativeLayout
        android:paddingBottom="5dp"
        android:paddingTop="5dp"
        android:paddingStart="10dp"
        android:paddingEnd="10dp"
        android:background="#66000000"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true">

        <!--當(dāng)前廣告的描述-->
        <TextView
            android:id="@+id/banner_describe_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#fff"
            android:text="廣告的描述"
            android:textSize="12dp" />

        <!--點(diǎn)布局-->
        <LinearLayout
            android:id="@+id/banner_dot_ll"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_centerVertical="true">

        </LinearLayout>
    </RelativeLayout>

</RelativeLayout>
```
  1. 初始化點(diǎn)的指示器和廣告位

    /**
     * 設(shè)置適配器
     */
    public void setAdapter(BannerAdapter adapter) {
        mBannerAdapter = adapter;
        mBannerVp.setAdapter(adapter);
    
        //初始化點(diǎn)的指示器
        initDotIndicator();
    
        //設(shè)置初始化的第一條廣告
        mBannerDescribeTv.setText(mBannerAdapter.getBannerDescribe(mCurrentDotPosition));
    
        //設(shè)置輪播后廣告和小點(diǎn)選中
        mBannerVp.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
            @Override
            public void onPageSelected(int position) {
                //監(jiān)聽當(dāng)前選中的位置帆焕,并改變小點(diǎn)狀態(tài)
                pageSelect(position % mBannerAdapter.getCount());
            }
        });
    }
    
    /**
     * 監(jiān)聽當(dāng)前選中的位置惭婿,并改變小點(diǎn)狀態(tài)
     * 設(shè)置廣告描述
     */
    private void pageSelect(int position) {
        //把之前的點(diǎn)變成未選中狀態(tài)
        DotIndicatorView oldDotView = (DotIndicatorView) mBannerDotLl.getChildAt(mCurrentDotPosition);
        oldDotView.setDrawable(mDotIndicatorNoSelectDrawable);
    
        //把position位置的點(diǎn)變成選中狀態(tài)
        mCurrentDotPosition = position;
        DotIndicatorView nowDotView = (DotIndicatorView) mBannerDotLl.getChildAt(mCurrentDotPosition);
        nowDotView.setDrawable(mDotIndicatorSelectDrawable);
    
        //設(shè)置廣告
        mBannerDescribeTv.setText(mBannerAdapter.getBannerDescribe(mCurrentDotPosition));
    }
    
    /**
     * 初始化點(diǎn)的指示器
     */
    private void initDotIndicator() {
        //獲取輪播圖數(shù)量,也就是要添加點(diǎn)的數(shù)量
        int dotCount = mBannerAdapter.getCount();
    
        //讓點(diǎn)的位置在右邊
        mBannerDotLl.setGravity(Gravity.RIGHT);
    
        for (int i = 0; i < dotCount; i++) {
            //不斷往點(diǎn)的指示器中添加點(diǎn)
            DotIndicatorView dotIndicatorView = new DotIndicatorView(mContext);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(dp2px(8), dp2px(8));
            //設(shè)置左右間距
            params.leftMargin = params.rightMargin = dp2px(3);
            //設(shè)置大小
            dotIndicatorView.setLayoutParams(params);
            //設(shè)置背景
            if (i == 0) {
                //選中點(diǎn)
                dotIndicatorView.setDrawable(mDotIndicatorSelectDrawable);
            } else {
                //未選中點(diǎn)
                dotIndicatorView.setDrawable(mDotIndicatorNoSelectDrawable);
            }
            //把點(diǎn)添加進(jìn)指示器中
            mBannerDotLl.addView(dotIndicatorView);
        }
    }
    
  2. 自定義屬性

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="BannerView">
            <!--點(diǎn)選中的顏色-->
            <attr name="dotIndicatorSelectColor" format="color|reference"/>
            <!--點(diǎn)未選中的顏色-->
            <attr name="dotIndicatorNoSelectColor" format="color|reference"/>
            <!--點(diǎn)的大小-->
            <attr name="dotSize" format="dimension"/>
            <!--點(diǎn)的間距-->
            <attr name="dotDistance" format="dimension"/>
            <!--點(diǎn)的位置-->
            <attr name="dotGravity" format="enum">
                <enum name="center" value="0"/>
                <enum name="right" value="1"/>
                <enum name="left" value="-1"/>
            </attr>
            <!--底部條顏色-->
            <attr name="bottomColor" format="color"/>
            <!--寬高比-->
            <attr name="wideProportion" format="float" />
            <attr name="heightProportion" format="float" />
        </declare-styleable>
    </resources>
    
  3. 初始化自定義屬性

    /**
     * 初始化自定義屬性
     */
    private void initAttribute(AttributeSet attrs) {
        TypedArray array = mContext.obtainStyledAttributes(attrs, R.styleable.BannerView);
    
        //獲取點(diǎn)的位置
        mDotGravity = array.getInt(R.styleable.BannerView_dotGravity, 1);
        //獲取小點(diǎn)選中和未選中樣式
        mDotIndicatorSelectDrawable = array.getDrawable(R.styleable.BannerView_dotIndicatorSelectColor);
        if (mDotIndicatorSelectDrawable == null) {
            mDotIndicatorSelectDrawable = new ColorDrawable(0xffff0000);
        }
        mDotIndicatorNoSelectDrawable = array.getDrawable(R.styleable.BannerView_dotIndicatorNoSelectColor);
        if (mDotIndicatorNoSelectDrawable == null) {
            mDotIndicatorNoSelectDrawable = new ColorDrawable(0xffffffff);
        }
        //點(diǎn)的大小
        mDotSize = (int) array.getDimension(R.styleable.BannerView_dotSize, 8);
        //點(diǎn)的間距
        mDotDistance = (int) array.getDimension(R.styleable.BannerView_dotDistance, 4);
        //底部條顏色
        mBottomColor = array.getColor(R.styleable.BannerView_bottomColor, 0x00000000);
        //寬高比
        mWideProportion = array.getFloat(R.styleable.BannerView_wideProportion, 0);
        mHeightProportion = array.getFloat(R.styleable.BannerView_heightProportion, 0);
    
        //最后array需要回收
        array.recycle();
    }
    

參考

Android無限廣告輪播 - 自定義BannerView

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叶雹,一起剝皮案震驚了整個濱河市财饥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌折晦,老刑警劉巖钥星,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異满着,居然都是意外死亡谦炒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門风喇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宁改,“玉大人,你說我怎么就攤上這事魂莫』苟祝” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵耙考,是天一觀的道長谜喊。 經(jīng)常有香客問我,道長倦始,這世上最難降的妖魔是什么斗遏? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮楣号,結(jié)果婚禮上最易,老公的妹妹穿的比我還像新娘。我一直安慰自己炫狱,他們只是感情好藻懒,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著视译,像睡著了一般嬉荆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酷含,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天鄙早,我揣著相機(jī)與錄音汪茧,去河邊找鬼。 笑死限番,一個胖子當(dāng)著我的面吹牛舱污,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弥虐,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼扩灯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了霜瘪?” 一聲冷哼從身側(cè)響起珠插,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎颖对,沒想到半個月后捻撑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缤底,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年顾患,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片训堆。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡描验,死狀恐怖白嘁,靈堂內(nèi)的尸體忽然破棺而出坑鱼,到底是詐尸還是另有隱情,我是刑警寧澤絮缅,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布鲁沥,位于F島的核電站,受9級特大地震影響耕魄,放射性物質(zhì)發(fā)生泄漏画恰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一吸奴、第九天 我趴在偏房一處隱蔽的房頂上張望允扇。 院中可真熱鬧,春花似錦则奥、人聲如沸考润。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽糊治。三九已至,卻和暖如春罚舱,著一層夾襖步出監(jiān)牢的瞬間井辜,已是汗流浹背绎谦。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粥脚,地道東北人窃肠。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像刷允,于是被迫代替她去往敵國和親铭拧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容