为什么写这篇文章,因为在网上看到的绝大多数BannerView实现了右无限轮播图,甚至没有实现无限轮播图,说成是无限轮播图,实现了左右无限轮播图的,并没有做性能上的优化。
先看张效果图
device-2018-05-11-173850.gif
工程目录图
project.png
BannerAdapter:banner轮播图的适配器,因为服务器返回的列表图片的url,显示的时候需要转成IamgeViw; BannerScroller:设置切换页面的持续时间; BannerView:继承RelativeLayout,包含BannViewPager和底部DotIndicatorView指示器; BannerViewPager:继承ViewPager,设置ViewPager的适配器Adpter和动画; DotIndicatorView:底部指示器;
DotIndicatorView类
public class DotIndicatorView extends View {//形状private int mShape;// 矩形public static final int SHAPE_REC = 1;// 圆形public static final int SHAPE_CIRCLE = 2;private Drawable mDrawable;public DotIndicatorView(Context context) { this(context, null);
}public DotIndicatorView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0);
}public DotIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DotIndicatorView); //默认是圆形
mShape = typedArray.getInteger(R.styleable.DotIndicatorView_shape, SHAPE_CIRCLE);
typedArray.recycle();
}@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mDrawable != null) {
Bitmap bitmap = drawableToBitmap(mDrawable); if (mShape == SHAPE_CIRCLE) {
Bitmap circleBitmap = getCircleBitmap(bitmap);
canvas.drawBitmap(circleBitmap, 0, 0, null);
} else if (mShape == SHAPE_REC) {
Bitmap recBitmap = getRecBitmap(bitmap);
canvas.drawBitmap(recBitmap, 0, 0, null);
}
}
}public void setDrawable(Drawable drawable) {
mDrawable = drawable;
invalidate();
}/**
* drawable转bitmap
*
* @param drawable
* @return
*/private Bitmap drawableToBitmap(Drawable drawable) { 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绘制在canvas内部
drawable.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
drawable.draw(canvas); return outBitmap;
}public void setShape(int shape) {
mShape = shape;
}public int getShape() { return mShape;
}/**
* 圆形
*
* @param bitmap
* @return
*/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); //设置画笔的图层,PorterDuff.Mode.SRC_IN 取图层交集的上层
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); //在把原来的bitmap绘制到圆上面
canvas.drawBitmap(bitmap, 0, 0, paint); //回收Bitmap
bitmap.recycle(); return circleBitmap;
}/**
* 带圆角的矩形
*
* @param bitmap
* @return
*/private Bitmap getRecBitmap(Bitmap bitmap) {
Bitmap recBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(recBitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true); //防止抖动
paint.setDither(true); //在画布上绘制一个圆角的矩形
canvas.drawRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()),
DensityUtil.dip2px(this.getContext(), 2), DensityUtil.dip2px(this.getContext(), 2), paint); //设置画笔的图层,PorterDuff.Mode.SRC_IN 取图层交集的上层
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); //在把原来的bitmap绘制到圆上面
canvas.drawBitmap(bitmap, 0, 0, paint); //回收Bitmap
bitmap.recycle(); return recBitmap;
}
}一般底部会有两种类型指示器,一是矩形的,二是圆形的,这个类实现了如何自定义矩形和圆形指示器,其实这个类也可以实现圆形的和带圆角的矩形的图片,用PorterDuffXfermode图层的概念。
BannerAdapter类
public abstract class BannerAdapter { /**
* 根据位置获取ViewPager的子View
*
* @param position
* @return
*/
public abstract View getView(int position, View convertView); /**
* 返回数量
*
* @return
*/
public abstract int getCount();
}BannerAdapter这个类是轮播图的适配器,因为服务器返回的列表图片的url,显示的时候需要转成IamgeViw,用适配器设计模式转一下。
BannerViewPager类
public class BannerViewPager extends ViewPager { private static final String TAG = BannerViewPager.class.getSimpleName(); private static final int SCROLL_MSG = 0x011; private BannerAdapter mBannerAdapter; private int mCutDownTime = 3000; private BannerScroller mBannerScroller; //内存优化界面复用
private List<View> mConvertView; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override
public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case SCROLL_MSG:
setCurrentItem(getCurrentItem() + 1);
startLoop(); break;
}
}
};public BannerViewPager(Context context) { this(context, null);
}public BannerViewPager(Context context, AttributeSet attrs) { super(context, attrs); //改变ViewPager切换的速率
try { //获取ViewPager的私有的属性mScroller
Field field = ViewPager.class.getDeclaredField("mScroller");
mBannerScroller = new BannerScroller(context); //设置强制改变
field.setAccessible(true); //设置参数 第一个参数object当前属性的那个类 第二参数需要设置的值
field.set(this, mBannerScroller);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
mConvertView = new ArrayList<>();
}/**
* 设置切换页面的持续时间
*
* @param scrollerDuration
*/public void setScrollerDuration(int scrollerDuration) {
mBannerScroller.setScrollerDuration(scrollerDuration);
}public void setAdapter(BannerAdapter adapter) { this.mBannerAdapter = adapter;
setAdapter(new BannerPagerAdapter()); //管理Activity的生命周期
(( Activity ) (getContext())).getApplication().registerActivityLifecycleCallbacks(mDefaultActivityLifecycleCallbacks);
}/**
* 开启轮播
*/public void startLoop() {
mHandler.removeMessages(SCROLL_MSG);
mHandler.sendEmptyMessageDelayed(SCROLL_MSG, mCutDownTime);
}/**
* 销毁Handler
*/@Overrideprotected void onDetachedFromWindow() { super.onDetachedFromWindow();
mHandler.removeMessages(SCROLL_MSG);
mHandler = null;
}private class BannerPagerAdapter extends PagerAdapter { /**
* 给一个很大的值,为了实现无限轮播
* 这个方法是返回ViewPager有多少个View
*/
@Override
public int getCount() { return Integer.MAX_VALUE;
} @NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) { //Adapter设计模式为了完全让用户自定义
//position 0-2的31次方
Log.i(TAG, "instantiateItem:position=" + position + "mBannerAdapter.getCount()=" + mBannerAdapter.getCount()); //position % mBannerAdapter.getCount() 求模
View bannerItemView = mBannerAdapter.getView(position % mBannerAdapter.getCount(), getConvertView());
container.addView(bannerItemView); return bannerItemView;
} @Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { return view == object;
} @Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView(( View ) object);
mConvertView.add(( View ) object);
}
}private float mDownX;@Overridepublic boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN:
mDownX = ev.getX();
mHandler.removeMessages(SCROLL_MSG); break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: //左滑动到第一张,跳转到getCount() - 1
if (this.getCurrentItem() == 0) { if (ev.getX() - mDownX > 0) { this.setCurrentItem(mBannerAdapter.getCount() - 1);
Log.i(TAG, "onTouchEvent: " + this.getCurrentItem());
}
}
mHandler.sendEmptyMessageDelayed(SCROLL_MSG, mCutDownTime); break;
} return super.onTouchEvent(ev);
}/**
* 处理页面复用
*
* @return
*/public View getConvertView() { for (int i = 0; i < mConvertView.size(); i++) { if (mConvertView.get(i).getParent() == null) { return mConvertView.get(i);
}
} return null;
}/**
* 管理Activity的生命周期
*/DefaultActivityLifecycleCallbacks mDefaultActivityLifecycleCallbacks = new DefaultActivityLifecycleCallbacks() { @Override
public void onActivityResumed(Activity activity) { super.onActivityResumed(activity); if (activity == getContext()) { //开启轮播
mHandler.sendEmptyMessageDelayed(SCROLL_MSG, mCutDownTime);
}
} @Override
public void onActivityPaused(Activity activity) { super.onActivityPaused(activity); if (activity == getContext()) { //停止轮播
mHandler.removeMessages(SCROLL_MSG);
}
}
};
}继承PagerAdapter实现getCount()这个方法,这个方法返回的是ViewPager有多少个View。为了实现无限轮播图返回了Integer.MAX_VALUE,用户不会手残一直向右滑动吧,造成溢出吧,哈。DefaultActivityLifecycleCallbacks 去监听Activity的生命周期,为什么要监听呢?因为当用户点击home键的时候,此时应用会在后台,但是ViewPager里面的ImageView还会循环,所以在Activity执行onPaused()的时候,停止轮播。getConvertView()这个方法是处理界面复用的,意思是跟RecycleView或者ListView实现列表滑动一样的,需要界面复用。最后,小编想实现一个左滑动到position=0,也就是第一张的时候,想跳转到getCount-1张,具体的做法是想在onTouchEvent()方法监听,手指按下记录下mDownX,手指抬起的时候ev.getX(),用ev.getX() - mDownX > 0坐下判断。在设置下 setCurrentItem(mBannerAdapter.getCount() - 1);
发现并没有实现。也不知道这是为什么,但是我认为这种思路没错,哪位大神看到了,请给出具体解决方案。
BannerView类
public class BannerView extends RelativeLayout { private BannerViewPager mBannerViewPager; //底部的指示器的View
private LinearLayout mDotContainerView; //适配器
private BannerAdapter mAdapter; private Context mContext; //选中的drawable
private Drawable mIndicatorFocusDrawable; //未被选中的drawable
private Drawable mIndicatorNormalDrawable; //当前页面的位置
private int mCurrentPosition; //指示器的位置
private int mDotGravity = -1; //指示器的大小
private int mDotSize = 6; //指示器的间距
private int mDotDistance = 2; //底部颜色默认透明
private int mBottomColor = Color.TRANSPARENT; private View mBannerBottomView; 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.banner_layout, this); this.mContext = context;
initAttribute(attrs);
initView();
} /**
* 初始化自定义属性
*
* @param attrs
*/
private void initAttribute(AttributeSet attrs) {
TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.BannerView);
mDotGravity = typedArray.getInt(R.styleable.BannerView_dotGravity, -1);
mIndicatorFocusDrawable = typedArray.getDrawable(R.styleable.BannerView_dotIndicatorFocus); if (mIndicatorFocusDrawable == null) {
mIndicatorFocusDrawable = new ColorDrawable(Color.RED);
}
mIndicatorNormalDrawable = typedArray.getDrawable(R.styleable.BannerView_dotIndicatorNormal); if (mIndicatorNormalDrawable == null) {
mIndicatorNormalDrawable = new ColorDrawable(Color.WHITE);
}
mDotSize = ( int ) typedArray.getDimension(R.styleable.BannerView_dotSize, DensityUtil.dip2px(mContext, 6));
mDotDistance = ( int ) typedArray.getDimension(R.styleable.BannerView_dotDistance, DensityUtil.dip2px(mContext, 2));
mBottomColor = typedArray.getColor(R.styleable.BannerView_bottomColor, mBottomColor);
typedArray.recycle();
} /**
* 初始化View
*/
private void initView() {
mBannerViewPager = findViewById(R.id.bannerViewPager);
mDotContainerView = findViewById(R.id.dot_container);
mBannerBottomView = findViewById(R.id.bannerBottomView);
mBannerBottomView.setBackgroundColor(mBottomColor);
mBannerViewPager.setPageTransformer(false, new SlidePageTransformer());
} /**
* 设置适配器adapter
*
* @param adapter 适配器
*/
public void setAdapter(BannerAdapter adapter) { this.mAdapter = adapter;
mBannerViewPager.setAdapter(adapter);
mBannerViewPager.setCurrentItem(mBannerViewPager.getChildCount() / 2);
initDotIndicator();
mBannerViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override
public void onPageSelected(int position) { //监听下当前的位置
super.onPageSelected(position);
DotIndicatorView dotIndicatorView = ( DotIndicatorView ) mDotContainerView.
getChildAt(mCurrentPosition);
dotIndicatorView.setDrawable(mIndicatorNormalDrawable);
mCurrentPosition = position % mAdapter.getCount();
DotIndicatorView mCurrentIndicatorView = ( DotIndicatorView ) mDotContainerView.
getChildAt(mCurrentPosition);
mCurrentIndicatorView.setDrawable(mIndicatorFocusDrawable);
}
});
} public void startLoop() {
mBannerViewPager.startLoop();
} public void setScrollerDuration(int scrollerDuration) {
mBannerViewPager.setScrollerDuration(scrollerDuration);
} /**
* 初始化指示器
*/
private void initDotIndicator() { //获取广告位的数量
int count = mAdapter.getCount(); //设置指示器的位置
mDotContainerView.setGravity(getDotGravity()); for (int i = 0; i < count; i++) {
DotIndicatorView dot = new DotIndicatorView(mContext); //设置指示器的形状
dot.setShape(1);
LinearLayout.LayoutParams param = null; //矩形
if (dot.getShape() == 1) { //给指示器指定大小
param = new LinearLayout.LayoutParams(mDotSize * 3, DensityUtil.dip2px(this.getContext(), 2)); //圆形
} else if (dot.getShape() == 2) {
param = new LinearLayout.LayoutParams(mDotSize, mDotSize);
} //设置间距
param.leftMargin = param.rightMargin = mDotDistance;
dot.setLayoutParams(param); if (i == 0) {
dot.setDrawable(mIndicatorFocusDrawable);
} else {
dot.setDrawable(mIndicatorNormalDrawable);
}
mDotContainerView.addView(dot);
}
} public int getDotGravity() { switch (mDotGravity) { case 0: return Gravity.CENTER; case 1: return Gravity.RIGHT; case -1: return Gravity.LEFT;
} return Gravity.RIGHT;
}SlidePageTransformer类
public class SlidePageTransformer implements ViewPager.PageTransformer {@Override public void transformPage(@NonNull View page, float position) { if (position > 0 && position <= 1) {
page.setPivotX(0);
page.setScaleX(1 - position);
} else if (position >= -1 && position < 0) {
page.setPivotX(page.getWidth());
page.setScaleX(1 + position);
}
}
}BannerView这个类主要是一些自定义属性,底部指示器的大小、颜色、间距等等。主要说下这个 mBannerViewPager.setPageTransformer(false, new SlidePageTransformer());这个给ViewPager设置了一个平滑的缩放的动画,但是看到了一个ViewPager设置动画的一个坑,发现滑到第一张的时候,在向右滑动的时候,图片会滑出一点边缘。也不知道为什么?我认为我的代码没有问题,也听说Android的源码ViewPager去设置动画,会有坑的存在。哪位大神看到了,望赐教!
共同學習,寫下你的評論
評論加載中...
作者其他優質文章

