Android无限广告轮播 - 自定义BannerView
1.概述
这其实是我第一篇想写的博客,可能是因为我遇到了太多的坑,那个时候刚入行下了很多Demo发现怎么也改不动,可能是能力有限,这次就做一个具体的实现和彻底的封装。
上次讲了Android无限广告轮播-ViewPager源码分析,有了源码分析我们对ViewPager就有了一个大概的了解,那么再来封装成自定义View,就会简单许多,附视频讲解地址:http://pan.baidu.com/s/1skOdHzn
![这里写图片描述](https://img-blog.csdn.net/20161118075829131)
2.效果封装
2.1 自定义BannerViewPager extends ViewPager:
我们要利用Adapter设计模式,那么目前这个阶段,需要的方法就是根据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);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
这样我们只要给他设置一个BannerAdapter就可以实现ViewPager的效果,可以手动切换,这里就先不看效果。
2.2. 实现自动轮播
实现自动轮播比较简单,实现的方式有多种可以用定时器Timer、Handler发送消息、start Thread的行,这里我采用Handler发送消息的方法。
private final int SCROLL_MSG = 0x0011;
private int mCutDownTime = 3500;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
setCurrentItem(getCurrentItem() + 1);
startRoll();
}
};
/**
* 2.实现自动轮播
*/
public void startRoll(){
mHandler.removeMessages(SCROLL_MSG);
mHandler.sendEmptyMessageDelayed(SCROLL_MSG,mCutDownTime);
Log.e(TAG,"startRoll");
}
/**
* 2.销毁Handler停止发送 解决内存泄漏
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mHandler.removeMessages(SCROLL_MSG);
mHandler = null;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
我们看一下效果吧,但是发现Gif录制根本捕捉不到切换的效果,因为自动切换速度太快了,这里还是不贴效果了,下面我就需要改变切换的速度。
2.3. 改变切换速率
如果看过上篇文章的源码就知道,我们会调用Scroller的mScroller.startScroll(sx, sy, dx, dy, duration)的这个方法,如果我们需要改变速率就只能改变duration执行切换页面动画的时间,可是我们根本拿不到这个值,那么就只能修改mScroller这个属性,可又发现他是private的有点头大,但是我们可以利用反射设置mScroller;
private BannerScroller mScroller;
public BannerViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
try {
Field field = ViewPager.class.getDeclaredField("mScroller");
mScroller = new BannerScroller(context);
field.setAccessible(true);
field.set(this,mScroller);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 3.设置切换页面动画持续的时间
*/
public void setScrollerDuration(int scrollerDuration){
mScroller.setScrollerDuration(scrollerDuration);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
现在效果差不多了,可以看到能够无限轮播,能够自动轮播,并且页面切换的速度也可以了,接下来就只需要处理点的指示器和文字的描述:
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{
private BannerViewPager mBannerVp;
private TextView mBannerDescTv;
private LinearLayout mDotContainerView;
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);
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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
2.5. 初始化点的指示器
/**
* 5.初始化点的指示器
*/
private void initDotIndicator() {
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());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
2.6. 阶段性的Bug修复
/**
* 4.设置适配器
*/
public void setAdapter(BannerAdapter adapter){
mAdapter = adapter;
mBannerVp.setAdapter(adapter);
initDotIndicator();
mBannerVp.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
@Override
public void onPageSelected(int position) {
pageSelect(position);
}
});
String firstDesc = mAdapter.getBannerDesc(0);
mBannerDescTv.setText(firstDesc);
}
/**
* 6.页面切换的回调
* @param position
*/
private void pageSelect(int position) {
DotIndicatorView oldIndicatorView = (DotIndicatorView)
mDotContainerView.getChildAt(mCurrentPosition);
oldIndicatorView.setDrawable(mIndicatorNormalDrawable);
mCurrentPosition = position%mAdapter.getCount();
DotIndicatorView currentIndicatorView = (DotIndicatorView)
mDotContainerView.getChildAt(mCurrentPosition);
currentIndicatorView.setDrawable(mIndicatorFocusDrawable);
String bannerDesc = mAdapter.getBannerDesc(mCurrentPosition);
mBannerDescTv.setText(bannerDesc);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
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){
Bitmap bitmap = drawableToBitmap(drawable);
Bitmap circleBitmap = getCircleBitmap(bitmap);
canvas.drawBitmap(circleBitmap,0,0,null);
}
}
/**
* 7.获取圆形bitmap
*/
private Bitmap getCircleBitmap(Bitmap 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);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap,0,0,paint);
return circleBitmap;
}
/**
* 7.从drawable中得到Bitmap
* @param drawable
* @return
*/
private Bitmap drawableToBitmap(Drawable drawable) {
if(drawable instanceof BitmapDrawable){
return((BitmapDrawable)drawable).getBitmap();
}
Bitmap outBitmap = Bitmap.createBitmap(getMeasuredWidth(),getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(outBitmap);
drawable.setBounds(0,0,getMeasuredWidth(),getMeasuredHeight());
drawable.draw(canvas);
return outBitmap;
}
/**
* 5.设置Drawable
*/
public void setDrawable(Drawable drawable) {
this.drawable = drawable;
invalidate();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
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();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
2.9. 自适应高度
if(mHeightProportion == 0 || mWidthProportion == 0){
return;
}
int width = getMeasuredWidth();
int height = (int) (width*mHeightProportion/mWidthProportion);
getLayoutParams().height = height;
2.10. 内存优化
写完之后,可以了就大功告成但是这个时候我们要去优化,还不好用不?容易扩展不?内存优化好没?在这里就不多写了,如回收Bitmap,界面复用,管理Activity生命周期等等,一切都在视频里面。
如果实在还是看不太懂,可以看一下我录的频,可以看一下整个系统架构也可以了解一下整个项目的其他东西:http://pan.baidu.com/s/1skOdHzn。