1.概述
这其实是我第一篇想写的博客,可能是因为我遇到了太多的坑,那个时候刚入行下了很多Demo发现怎么也改不动,可能是能力有限,这次就做一个具体的实现和彻底的封装。 上次讲了Android无限广告轮播-ViewPager源码分析,有了源码分析我们对ViewPager就有了一个大概的了解,那么再来封装成自定义View,就会简单许多,附视频讲解地址:http://pan.baidu.com/s/1skOdHzn
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 {@Overridepublic int getCount() {// 返回一个很大的值,确保可以无限轮播return Integer.MAX_VALUE;}@Overridepublic boolean isViewFromObject(View view, Object object) {// 这么写就对了,看了源码应该就明白return view == object;}@Overridepublic Object instantiateItem(ViewGroup container, final int position) {View bannerView = mAdapter.getView(position);container.addView(bannerView );return bannerView;}@Overridepublic void destroyItem(ViewGroup container, int position, Object object) {// 销毁回调的方法 移除页面即可container.removeView((View) object);}}
}
复制代码
这样我们只要给他设置一个BannerAdapter就可以实现ViewPager的效果,可以手动切换,这里就先不看效果。 2.2. 实现自动轮播
实现自动轮播比较简单,实现的方式有多种可以用定时器Timer、Handler发送消息、start Thread的行,这里我采用Handler发送消息的方法。
// 2.实现自动轮播 - 发送消息的msgWhatprivate final int SCROLL_MSG = 0x0011;// 2.实现自动轮播 - 页面切换间隔时间private int mCutDownTime = 3500;// 2.实现自动轮播 - 发送消息Handlerprivate Handler mHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {// 每隔*s后切换到下一页setCurrentItem(getCurrentItem() + 1);// 不断循环执行startRoll();}};/*** 2.实现自动轮播*/public void startRoll(){// 清除消息mHandler.removeMessages(SCROLL_MSG);// 消息 延迟时间 让用户自定义 有一个默认 3500mHandler.sendEmptyMessageDelayed(SCROLL_MSG,mCutDownTime);Log.e(TAG,"startRoll");}/*** 2.销毁Handler停止发送 解决内存泄漏*/@Overrideprotected 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切换的速率 - 自定义的页面切换的Scrollerprivate 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);// 设置为强制改变privatefield.setAccessible(true);field.set(this,mScroller);} catch (Exception e) {e.printStackTrace();}}/*** 3.设置切换页面动画持续的时间*/public void setScrollerDuration(int scrollerDuration){mScroller.setScrollerDuration(scrollerDuration);}
复制代码
现在效果差不多了,可以看到能够无限轮播,能够自动轮播,并且页面切换的速度也可以了,接下来就只需要处理点的指示器和文字的描述:
接下来我们又自定义一个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 - 轮播的ViewPagerprivate BannerViewPager mBannerVp;// 4.自定义BannerView - 轮播的描述private TextView mBannerDescTv;// 4.自定义BannerView - 点的容器private LinearLayout mDotContainerView;// 4.自定义BannerView - 自定义的BannerAdapterprivate 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(){@Overridepublic 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);}@Overrideprotected 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) {// 创建一个BitmapBitmap 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// 创建一个什么也没有的bitmapBitmap 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;// 重新绘制Viewinvalidate();}
}复制代码
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生命周期等等,一切都在视频里面。