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源碼分析
實(shí)現(xiàn)
-
自定義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; } });
-
實(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; }
-
改變切換的速率
在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); }
-
自定義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>
```
-
初始化點(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); } }
-
自定義屬性
<?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>
-
初始化自定義屬性
/** * 初始化自定義屬性 */ 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(); }