1.概述
這其實是我第一篇想寫的博客,可能是因為我遇到了太多的坑灭贷,那個時候剛入行下了很多Demo發(fā)現(xiàn)怎么也改不動略贮,可能是能力有限逃延,這次就做一個具體的實現(xiàn)和徹底的封裝。
上次講了Android無限廣告輪播-ViewPager源碼分析讽膏,有了源碼分析我們對ViewPager就有了一個大概的了解拄丰,那么再來封裝成自定義View料按,就會簡單許多,附視頻講解地址:http://pan.baidu.com/s/1skOdHzn
2.效果封裝
2.1 自定義BannerViewPager extends ViewPager:
我們要利用Adapter設計模式,那么目前這個階段,需要的方法就是根據(jù)PagerAdapter位置獲取當前View甫题,所以BannerAdapter里面就只需要一個方法那就是getView(int position);
/**
* description:
* 廣告輪播的ViewPager
* Created by 曾輝 on 2016/11/17.
* QQ:240336124
* Email: 240336124@qq.com
* Version:1.0
*/
public class BannerViewPager extends ViewPager {
private Context mContext;
private BannerAdapter mAdapter;
public BannerViewPager(Context context) {
this(context, null);
}
public BannerViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
}
public void setAdapter(BannerAdapter adapter) {
this.mAdapter = adapter;
setAdapter(new BannerPagerAdapter());
}
private class BannerPagerAdapter extends PagerAdapter {
@Override
public int getCount() {
// 返回一個很大的值坠非,確惫可以無限輪播
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
// 這么寫就對了秋泳,看了源碼應該就明白
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, final int position) {
View bannerView = mAdapter.getView(position);
container.addView(bannerView );
return bannerView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
// 銷毀回調的方法 移除頁面即可
container.removeView((View) object);
}
}
}
這樣我們只要給他設置一個BannerAdapter就可以實現(xiàn)ViewPager的效果迫皱,可以手動切換,這里就先不看效果和敬。
2.2. 實現(xiàn)自動輪播
實現(xiàn)自動輪播比較簡單戏阅,實現(xiàn)的方式有多種可以用定時器Timer奕筐、Handler發(fā)送消息、start Thread的行芭逝,這里我采用Handler發(fā)送消息的方法旬盯。
// 2.實現(xiàn)自動輪播 - 發(fā)送消息的msgWhat
private final int SCROLL_MSG = 0x0011;
// 2.實現(xiàn)自動輪播 - 頁面切換間隔時間
private int mCutDownTime = 3500;
// 2.實現(xiàn)自動輪播 - 發(fā)送消息Handler
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
// 每隔*s后切換到下一頁
setCurrentItem(getCurrentItem() + 1);
// 不斷循環(huán)執(zhí)行
startRoll();
}
};
/**
* 2.實現(xiàn)自動輪播
*/
public void startRoll(){
// 清除消息
mHandler.removeMessages(SCROLL_MSG);
// 消息 延遲時間 讓用戶自定義 有一個默認 3500
mHandler.sendEmptyMessageDelayed(SCROLL_MSG,mCutDownTime);
Log.e(TAG,"startRoll");
}
/**
* 2.銷毀Handler停止發(fā)送 解決內存泄漏
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mHandler.removeMessages(SCROLL_MSG);
mHandler = null;
}
我們看一下效果吧瓢捉,但是發(fā)現(xiàn)Gif錄制根本捕捉不到切換的效果办成,因為自動切換速度太快了搂漠,這里還是不貼效果了,下面我就需要改變切換的速度靶壮。
2.3. 改變切換速率
如果看過上篇文章的源碼就知道员萍,我們會調用Scroller的mScroller.startScroll(sx, sy, dx, dy, duration)的這個方法碎绎,如果我們需要改變速率就只能改變duration執(zhí)行切換頁面動畫的時間,可是我們根本拿不到這個值奸晴,那么就只能修改mScroller這個屬性日麸,可又發(fā)現(xiàn)他是private的有點頭大代箭,但是我們可以利用反射設置mScroller;
// 3.改變ViewPager切換的速率 - 自定義的頁面切換的Scroller
private BannerScroller mScroller;
public BannerViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
try {
// 3.改變ViewPager切換的速率
// 3.1 duration 持續(xù)的時間 局部變量
// 3.2.改變 mScroller private 通過反射設置
Field field = ViewPager.class.getDeclaredField("mScroller");
// 設置參數(shù) 第一個object當前屬性在哪個類 第二個參數(shù)代表要設置的值
mScroller = new BannerScroller(context);
// 設置為強制改變private
field.setAccessible(true);
field.set(this,mScroller);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 3.設置切換頁面動畫持續(xù)的時間
*/
public void setScrollerDuration(int scrollerDuration){
mScroller.setScrollerDuration(scrollerDuration);
}
現(xiàn)在效果差不多了嗡综,可以看到能夠無限輪播,能夠自動輪播蚣旱,并且頁面切換的速度也可以了戴陡,接下來就只需要處理點的指示器和文字的描述:
2.4. 自定義BannerView加入點指示和廣告描述
接下來我們又自定義一個BannerView里面包含當前自定義好的BannerViewPager和點的指示LinearLayout以及廣告描述TextView恤批。
package com.example.hui.androidtemplate.banner;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.example.hui.androidtemplate.R;
/**
* description:
* <p/>
* Created by 曾輝 on 2016/11/18.
* QQ:240336124
* Email: 240336124@qq.com
* Version:1.0
*/
public class BannerView extends RelativeLayout{
// 4.自定義BannerView - 輪播的ViewPager
private BannerViewPager mBannerVp;
// 4.自定義BannerView - 輪播的描述
private TextView mBannerDescTv;
// 4.自定義BannerView - 點的容器
private LinearLayout mDotContainerView;
// 4.自定義BannerView - 自定義的BannerAdapter
private BannerAdapter mAdapter;
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);
// 把布局加載到這個View里面
inflate(context, R.layout.ui_banner_layout,this);
initView();
}
/**
* 初始化View
*/
private void initView() {
mBannerVp = (BannerViewPager) findViewById(R.id.banner_vp);
mBannerDescTv = (TextView) findViewById(R.id.banner_desc_tv);
mDotContainerView = (LinearLayout) findViewById(R.id.dot_container);
}
/**
* 4.設置適配器
*/
public void setAdapter(BannerAdapter adapter){
mBannerVp.setAdapter(adapter);
}
/**
* 4.開始滾動
*/
public void startRoll() {
mBannerVp.startRoll();
}
}
2.5. 初始化點的指示器
/**
* 5.初始化點的指示器
*/
private void initDotIndicator() {
// 獲取廣告的數(shù)量
int count = mAdapter.getCount();
// 讓點的位置在右邊
mDotContainerView.setGravity(Gravity.RIGHT);
for (int i = 0;i<count;i++){
// 不斷的往點的指示器添加圓點
DotIndicatorView indicatorView = new DotIndicatorView(mContext);
// 設置大小
LinearLayout.LayoutParams params = new
LinearLayout.LayoutParams(dip2px(8),dip2px(8));
// 設置左右間距
params.leftMargin = params.rightMargin = dip2px(2);
indicatorView.setLayoutParams(params);
if(i == 0) {
// 選中位置
indicatorView.setDrawable(mIndicatorFocusDrawable);
}else{
// 未選中的
indicatorView.setDrawable(mIndicatorNormalDrawable);
}
mDotContainerView.addView(indicatorView);
}
}
/**
* 5.把dip轉成px
*/
private int dip2px(int dip) {
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dip,getResources().getDisplayMetrics());
}
2.6. 階段性的Bug修復
/**
* 4.設置適配器
*/
public void setAdapter(BannerAdapter adapter){
mAdapter = adapter;
mBannerVp.setAdapter(adapter);
// 5.初始化點的指示器
initDotIndicator();
// 6.Bug修復
mBannerVp.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
@Override
public void onPageSelected(int position) {
// 監(jiān)聽當前選中的位置
pageSelect(position);
}
});
// 6.初始化的時候獲取第一條的描述
String firstDesc = mAdapter.getBannerDesc(0);
mBannerDescTv.setText(firstDesc);
}
/**
* 6.頁面切換的回調
* @param position
*/
private void pageSelect(int position) {
// 6.1 把之前亮著的點 設置為默認
DotIndicatorView oldIndicatorView = (DotIndicatorView)
mDotContainerView.getChildAt(mCurrentPosition);
oldIndicatorView.setDrawable(mIndicatorNormalDrawable);
// 6.2 把當前位置的點 點亮 position 0 --> 2的31次方
mCurrentPosition = position%mAdapter.getCount();
DotIndicatorView currentIndicatorView = (DotIndicatorView)
mDotContainerView.getChildAt(mCurrentPosition);
currentIndicatorView.setDrawable(mIndicatorFocusDrawable);
// 6.3設置廣告描述
String bannerDesc = mAdapter.getBannerDesc(mCurrentPosition);
mBannerDescTv.setText(bannerDesc);
}
2.7. 把指示器的點繪制成圓
/**
* description: 圓的指示器
* 圓點指示器
* Created by 曾輝 on 2016/11/18.
* QQ:240336124
* Email: 240336124@qq.com
* Version:1.0
*/
public class DotIndicatorView extends View {
private Drawable drawable;
public DotIndicatorView(Context context) {
this(context, null);
}
public DotIndicatorView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DotIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
if(drawable != null){
/*drawable.setBounds(0,0,getMeasuredWidth(),getMeasuredHeight());
drawable.draw(canvas);*/
// 7.把指示器變成圓形
// 畫圓
Bitmap bitmap = drawableToBitmap(drawable);
// 把Bitmap變?yōu)閳A的
Bitmap circleBitmap = getCircleBitmap(bitmap);
// 把圓形的Bitmap繪制到畫布上
canvas.drawBitmap(circleBitmap,0,0,null);
}
}
/**
* 7.獲取圓形bitmap
*/
private Bitmap getCircleBitmap(Bitmap bitmap) {
// 創(chuàng)建一個Bitmap
Bitmap circleBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(circleBitmap);
Paint paint = new Paint();
// 設置抗鋸齒
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
// 設置仿抖動
paint.setDither(true);
// 在畫布上面畫個圓
canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,getMeasuredWidth()/2,paint);
// 取圓和Bitmap矩形的交集
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// 再把原來的Bitmap繪制到新的圓上面
canvas.drawBitmap(bitmap,0,0,paint);
return circleBitmap;
}
/**
* 7.從drawable中得到Bitmap
* @param drawable
* @return
*/
private Bitmap drawableToBitmap(Drawable drawable) {
// 如果是BitmapDrawable類型
if(drawable instanceof BitmapDrawable){
return((BitmapDrawable)drawable).getBitmap();
}
// 其他類型 ColorDrawable
// 創(chuàng)建一個什么也沒有的bitmap
Bitmap outBitmap = Bitmap.createBitmap(getMeasuredWidth(),getMeasuredHeight(), Bitmap.Config.ARGB_8888);
// 創(chuàng)建一個畫布
Canvas canvas = new Canvas(outBitmap);
// 把drawable化到Bitmap上
drawable.setBounds(0,0,getMeasuredWidth(),getMeasuredHeight());
drawable.draw(canvas);
return outBitmap;
}
/**
* 5.設置Drawable
*/
public void setDrawable(Drawable drawable) {
this.drawable = drawable;
// 重新繪制View
invalidate();
}
}
2.8. 設置自定義屬性
/**
* 8.初始化自定義屬性
*/
private void initAttribute(AttributeSet attrs) {
TypedArray array = mContext.obtainStyledAttributes(attrs, R.styleable.BannerView);
// 獲取點的位置
mDotGravity = array.getInt(R.styleable.BannerView_dotGravity, mDotGravity);
// 獲取點的顏色(默認、選中)
mIndicatorFocusDrawable = array.getDrawable(R.styleable.BannerView_dotIndicatorFocus);
if(mIndicatorFocusDrawable == null){
// 如果在布局文件中沒有配置點的顏色 有一個默認值
mIndicatorFocusDrawable = new ColorDrawable(Color.RED);
}
mIndicatorNormalDrawable = array.getDrawable(R.styleable.BannerView_dotIndicatorNormal);
if(mIndicatorNormalDrawable == null){
// 如果在布局文件中沒有配置點的顏色 有一個默認值
mIndicatorNormalDrawable = new ColorDrawable(Color.WHITE);
}
// 獲取點的大小和距離
mDotSize = (int) array.getDimension(R.styleable.BannerView_dotSize,dip2px(mDotSize));
mDotDistance = (int) array.getDimension(R.styleable.BannerView_dotDistance,dip2px(mDotDistance));
array.recycle();
}
2.9. 自適應高度
// 8.自適應高度 動態(tài)指定高度
if(mHeightProportion == 0 || mWidthProportion == 0){
return;
}
// 動態(tài)指定寬高 計算高度
int width = getMeasuredWidth();
// 計算高度
int height = (int) (width*mHeightProportion/mWidthProportion);
// 指定寬高
getLayoutParams().height = height;
2.10. 內存優(yōu)化
寫完之后求摇,可以了就大功告成但是這個時候我們要去優(yōu)化与境,還不好用不?容易擴展不挥转?內存優(yōu)化好沒共屈?在這里就不多寫了,如回收Bitmap域仇,界面復用寺擂,管理Activity生命周期等等怔软,一切都在視頻里面。
如果實在還是看不太懂括改,可以看一下我錄的頻嘱能,可以看一下整個系統(tǒng)架構也可以了解一下整個項目的其他東西:http://pan.baidu.com/s/1skOdHzn虱疏。