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

书到用时方恨少,事非经过不知难。这篇文章主要讲述Android无限广告轮播 - 自定义BannerView相关的知识,希望能为你提供帮助。
1.概述
这其实是我第一篇想写的博客, 可能是因为我遇到了太多的坑, 那个时候刚入行下了很多Demo发现怎么也改不动, 可能是能力有限, 这次就做一个具体的实现和彻底的封装。
上次讲了Android无限广告轮播-ViewPager源码分析, 有了源码分析我们对ViewPager就有了一个大概的了解, 那么再来封装成自定义View, 就会简单许多, 附视频讲解地址: http://pan.baidu.com/s/1skOdHzn

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

文章图片

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); } } }

【Android无限广告轮播 - 自定义BannerView】这样我们只要给他设置一个BannerAdapter就可以实现ViewPager的效果, 可以手动切换, 这里就先不看效果。


2.2. 实现自动轮播
实现自动轮播比较简单, 实现的方式有多种可以用定时器Timer、Handler发送消息、start Thread的行, 这里我采用Handler发送消息的方法。
// 2.实现自动轮播 - 发送消息的msgWhat private final int SCROLL_MSG = 0x0011; // 2.实现自动轮播 - 页面切换间隔时间 private int mCutDownTime = 3500; // 2.实现自动轮播 - 发送消息Handler private Handler mHandler = new Handler(){ @ Override public void handleMessage(Message msg) { // 每隔*s后切换到下一页 setCurrentItem(getCurrentItem() + 1); // 不断循环执行 startRoll(); } }; /** * 2.实现自动轮播 */ public void startRoll(){ // 清除消息 mHandler.removeMessages(SCROLL_MSG); // 消息延迟时间让用户自定义有一个默认3500 mHandler.sendEmptyMessageDelayed(SCROLL_MSG,mCutDownTime); Log.e(TAG," startRoll" ); }/** * 2.销毁Handler停止发送解决内存泄漏 */ @ Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mHandler.removeMessages(SCROLL_MSG); mHandler = null; }

我们看一下效果吧, 但是发现Gif录制根本捕捉不到切换的效果, 因为自动切换速度太快了, 这里还是不贴效果了, 下面我就需要改变切换的速度。

2.3. 改变切换速率
如果看过上篇文章的源码就知道, 我们会调用Scroller的mScroller.startScroll(sx, sy, dx, dy, duration)的这个方法, 如果我们需要改变速率就只能改变duration执行切换页面动画的时间, 可是我们根本拿不到这个值, 那么就只能修改mScroller这个属性, 可又发现他是private的有点头大, 但是我们可以利用反射设置mScroller;
// 3.改变ViewPager切换的速率 - 自定义的页面切换的Scroller private BannerScroller mScroller; public BannerViewPager(Context context, AttributeSet attrs) { super(context, attrs); try { // 3.改变ViewPager切换的速率 // 3.1 duration 持续的时间局部变量 // 3.2.改变 mScroller private 通过反射设置 Field field = ViewPager.class.getDeclaredField(" mScroller" ); // 设置参数第一个object当前属性在哪个类第二个参数代表要设置的值 mScroller = new BannerScroller(context); // 设置为强制改变private field.setAccessible(true); field.set(this,mScroller); } catch (Exception e) { e.printStackTrace(); } }/** * 3.设置切换页面动画持续的时间 */ public void setScrollerDuration(int scrollerDuration){ mScroller.setScrollerDuration(scrollerDuration); }

现在效果差不多了, 可以看到能够无限轮播, 能够自动轮播, 并且页面切换的速度也可以了, 接下来就只需要处理点的指示器和文字的描述:
Android无限广告轮播 - 自定义BannerView

文章图片


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() { // 获取广告的数量 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) { // 监听当前选中的位置 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变为圆的 Bitmap circleBitmap = getCircleBitmap(bitmap); // 把圆形的Bitmap绘制到画布上 canvas.drawBitmap(circleBitmap,0,0,null); } }/** * 7.获取圆形bitmap */ private Bitmap getCircleBitmap(Bitmap 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); // 取圆和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 // 创建一个什么也没有的bitmap Bitmap outBitmap = Bitmap.createBitmap(getMeasuredWidth(),getMeasuredHeight(), Bitmap.Config.ARGB_8888); // 创建一个画布 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.自适应高度 动态指定高度 if(mHeightProportion = = 0 || mWidthProportion = = 0){ return; } // 动态指定宽高计算高度 int width = getMeasuredWidth(); // 计算高度 int height = (int) (width*mHeightProportion/mWidthProportion); // 指定宽高 getLayoutParams().height = height;

2.10. 内存优化
写完之后, 可以了就大功告成但是这个时候我们要去优化, 还不好用不? 容易扩展不? 内存优化好没? 在这里就不多写了, 如回收Bitmap, 界面复用, 管理Activity生命周期等等, 一切都在视频里面。
Android无限广告轮播 - 自定义BannerView

文章图片


如果实在还是看不太懂, 可以看一下我录的频, 可以看一下整个系统架构也可以了解一下整个项目的其他东西: http://pan.baidu.com/s/1skOdHzn。


    推荐阅读