Android广告轮播
广告轮播功能在很多项目中多用得到,最近看了一位大神的博客,仿照他的文章,自己来写一个广告轮播功能,写的过程中学到了很多东西,谢谢。
首先来看下核心的类BannerViewPager:
public class BannerViewPager extends ViewPager {
private String TAG="BannerViewPager";
Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
setCurrentItem(getCurrentItem()+1);
startRoll();
}
};
//消息
private static final int SCROLL_MESSAGE_WHAT=0X0005;
//轮播间隔
private int SCROLL_INTERVAL_TIME=3500;
//adapter
private BannerAdapter mBannerAdapter;
private Context mContext;
//界面复用
private List<View> mConvertViews;
public BannerViewPager(Context context) {
this(context,null);
}
public BannerViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
mContext=context;
try {
//利用发射,改变滑动时间
BannerScroller mBannerScroller=new BannerScroller(context);
Field field =
ViewPager.class.getDeclaredField("mScroller");
if(!field.isAccessible())
field.setAccessible(true);
field.set(this,mBannerScroller);
} catch (Exception e) {
e.printStackTrace();
}
//初始化界面复用列表
mConvertViews=new ArrayList<>();
//注册生命周期处理
getActivity().getApplication().registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public Activity getActivity(){
return (Activity) mContext;
}
/**
* 开始轮播
*/
public void startRoll(){
mHandler.removeMessages(SCROLL_MESSAGE_WHAT);
mHandler.sendEmptyMessageDelayed(SCROLL_MESSAGE_WHAT,SCROLL_INTERVAL_TIME);
Log.e(TAG,"====>startRoll");
}
//销毁handler的发送
@Override
protected void onDetachedFromWindow() {
//销毁handler的发送,防止内存泄漏
mHandler.removeMessages(SCROLL_MESSAGE_WHAT);
mHandler=null;
//取消注册生命周期处理
getActivity().getApplication().unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
super.onDetachedFromWindow();
}
public void setAdapter(BannerAdapter adapter) {
this.mBannerAdapter=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, int position) {
if(mBannerAdapter.getCount()<=0){
return null;
}
View view=mBannerAdapter.getView(position % mBannerAdapter.getCount(),getConvertView());
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
mConvertViews.add((View) object);
}
}
/**
* 获取复用界面
* @return
*/
private View getConvertView(){
if(mConvertViews==null){
return null;
}
for (View view : mConvertViews) {
if(view.getParent()==null){
return view;
}
}
return null;
}
/**
* activty生命周期
*/
Application.ActivityLifecycleCallbacks lifecycleCallbacks=new SimpleActivityLifecycleCallbacks(){
@Override
public void onActivityResumed(Activity activity) {
super.onActivityResumed(activity);
Log.e(TAG,"====>onActivityResumed");
if(activity == getActivity()){
mHandler.sendEmptyMessageDelayed(SCROLL_MESSAGE_WHAT,SCROLL_INTERVAL_TIME);
}
}
@Override
public void onActivityPaused(Activity activity) {
super.onActivityPaused(activity);
Log.e(TAG,"====>onActivityPaused");
if(activity == getActivity()){
mHandler.removeMessages(SCROLL_MESSAGE_WHAT);
}
}
};
}
上面代码要主要几点:
1.handler 的处理,在onDetachedFromWindow中消耗handler,防止内存泄露
2.onActivityResumed和onActivityPaused中对轮播进行开启和停止,防止切换到别的页面,当前页还在继续播放,减少系统开销
3.反射改变mScroller对象,修改滑动时间
4.和listView相同,使用了view复用,减少系统开销
自定义适配器:
public abstract class BannerAdapter {
/**
* 创建BannerViewPager 的view
* @param position
* @param convertView 复用View
* @return
*/
public abstract View getView(int position, View convertView);
/**
* 播放条数
* @return
*/
public abstract int getCount();
/**
* 广告位
* @param position
* @return
*/
public String getBannerDesc(int position) {
return "";
}
}
重写Scroller的startScroll方法,因为viewpager就是使用这个方法来进行滚动的
public class BannerScroller extends Scroller {
private int mBannerDuration=850;
public void setBannerDuration(int mBannerDuration) {
this.mBannerDuration = mBannerDuration;
}
public BannerScroller(Context context) {
super(context);
}
public BannerScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, mBannerDuration);
}
}
对BinnerViewPager 再次重装,添加了广告位和轮播点
public class BannerView extends RelativeLayout {
//banner viewpager
private BannerViewPager mBannerViewPager;
//点指示器容器
private LinearLayout mDotContainer;
//适配器
private BannerAdapter mAdapter=null;
private Context mContext;
//正常点的drawable
private Drawable mIndicatorNormalDrawable;
//被点击点的drawable
private Drawable mIndicatorFocusDrawable;
//当前选中指示器位置
private int mCurrentPostion=0;
private TextView mDescView;
//点布局位置
int mDotGravity=1;
//点大小
int mDotSize=8;
//点之间的距离
int mDotDistance=2;
//宽高比
float mWidthProportion=0f;
float mHeightProportion=0f;
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);
this.mContext=context;
inflate(context, R.layout.ui_bannerview_layout,this);
init();
initAttribute(attrs);
}
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)); // 获取底部的颜色 mBottomColor = array.getColor(R.styleable.BannerView_bottomColor,mBottomColor);
//获取宽高比例
mWidthProportion = array.getFloat(R.styleable.BannerView_withProportion,mWidthProportion);
mHeightProportion = array.getFloat(R.styleable.BannerView_heightProportion,mHeightProportion);
array.recycle();
}
private void init() {
mBannerViewPager = (BannerViewPager) findViewById(R.id.banner_viewpager);
mDotContainer = (LinearLayout) findViewById(R.id.dot_container);
mDescView = (TextView) findViewById(R.id.tv_desc);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(mHeightProportion != 0 && mWidthProportion != 0){
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = (int) (width*mHeightProportion/mWidthProportion);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 设置适配器
* @param adapter
*/
public void setAdapter(final BannerAdapter adapter){
this.mAdapter=adapter;
mBannerViewPager.setAdapter(adapter);
initDotIndicator();
initDesc();
mBannerViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
@Override
public void onPageSelected(int position) {
pageSelected(position % adapter.getCount());
}
});
}
/**
* 初始化广告位置
*/
private void initDesc() {
String desc = mAdapter.getBannerDesc(0);
if(!TextUtils.isEmpty(desc)) {
mDescView.setText(desc);
}else {
mDescView.setText("");
}
}
/**
* 轮播页切换
* @param position
*/
private void pageSelected(int position) {
int oldPostion=mCurrentPostion;
DotIndicatorView oldView = (DotIndicatorView) mDotContainer.getChildAt(oldPostion);
oldView.setDrawable(mIndicatorNormalDrawable);
DotIndicatorView curView = (DotIndicatorView) mDotContainer.getChildAt(position);
curView.setDrawable(mIndicatorFocusDrawable);
mCurrentPostion=position;
//设置描述
String desc = mAdapter.getBannerDesc(position);
if(!TextUtils.isEmpty(desc)) {
mDescView.setText(desc);
}else {
mDescView.setText("");
}
}
/**
* 初始化指示器
*/
private void initDotIndicator() {
mDotContainer.setGravity(obtainGravity());
int count = mAdapter.getCount();
for (int i = 0; i < count; i++) {
DotIndicatorView dotView = new DotIndicatorView(mContext);
LinearLayout.LayoutParams layoutParams=new LinearLayout.LayoutParams(mDotSize,mDotSize);
layoutParams.leftMargin=layoutParams.rightMargin=mDotDistance;
dotView.setLayoutParams(layoutParams);
mCurrentPostion=0;
if(i==0) {
dotView.setDrawable(mIndicatorFocusDrawable);
}else {
dotView.setDrawable(mIndicatorNormalDrawable);
}
mDotContainer.addView(dotView);
}
}
private int obtainGravity(){
switch (mDotGravity){
case 0:
return Gravity.CENTER;
case -1:
return Gravity.LEFT;
default:
return Gravity.RIGHT;
}
}
/**
* dip 2 px
* @param dip
* @return
*/
private int dip2px(int dip) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dip,getResources().getDisplayMetrics());
}
public void startRoll(){
mBannerViewPager.startRoll();
}
}
注意点:根据给定的宽高比,对当前高度进行测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(mHeightProportion != 0 && mWidthProportion != 0){
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = (int) (width*mHeightProportion/mWidthProportion);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
轮播点的绘制
public class DotIndicatorView extends View {
private Drawable drawable;
public DotIndicatorView(Context context) {
super(context);
}
public DotIndicatorView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public DotIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
Bitmap bitmap = drawable2Bitmap(drawable);
Bitmap circleBitmap= getCircleBitmap(bitmap);
canvas.drawBitmap(circleBitmap,0,0,null);
}
private Bitmap getCircleBitmap(Bitmap bitmap) {
Bitmap bm = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas=new Canvas(bm);
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 bm;
}
/**
* drawble 转 bitmap
* @param drawable
* @return
*/
private Bitmap drawable2Bitmap(Drawable drawable){
if(drawable instanceof BitmapDrawable){
return ((BitmapDrawable)drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas=new Canvas(bitmap);
drawable.setBounds(0,0,getMeasuredWidth(),getMeasuredHeight());
drawable.draw(canvas);
return bitmap;
}
public void setDrawable(Drawable drawable) {
this.drawable = drawable;
invalidate();
}
}
将用户给的drawable(colordrawable ,bitmapdrawable,其他)绘制到画布上。
下面,我们来看使用:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main"
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical">
<github.com.bannerviewlibrary.BannerView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/banner_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:withProportion="8"
app:heightProportion="4"
app:dotSize="8dp"
app:dotDistance="3dp"
app:dotGravity="right"
app:bottomColor="@color/banner_bottom_bar_bg_day"
app:dotIndicatorFocus="@color/dot_select_color"
app:dotIndicatorNormal="@color/dot_unselect_color">
</github.com.bannerviewlibrary.BannerView>
<Button
android:id="@+id/btn_jump"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳转"/>
</LinearLayout>
mainactivity中:
int[] mData=new int[]{R.mipmap.banner_default,R.mipmap.splash_slogan};
String[] mDesc=new String[]{"哈哈哈哈哈哈哈","呵呵呵呵呵呵呵呵呵呵呵"};
private void initData() {
mBannerView.setAdapter(new BannerAdapter() {
@Override
public View getView(int position, View convertView) {
ImageView mImageView=null;
if(convertView==null){
mImageView=new ImageView(mActivity);
}else {
mImageView= (ImageView) convertView;
}
mImageView.setImageResource(mData[position]);
return mImageView;
}
@Override
public int getCount() {
return mData.length;
}
@Override
public String getBannerDesc(int position) {
return mDesc[position];
}
});
mBannerView.startRoll();
}
使用起来还是很简单的。
我们来看下效果:
下载地址:BannerView