转载请注明出处
注意这是个自定义View而不是自定义ViewGroup
使用也很简单就一行代码就搞定bannerView.setBitmaps(bitmaps).start();
setBitmaps和start两个方法比较简单,就是设置数据源和调用invalidate(),代码如下:
public BannerView setBitmaps(ArrayList<Bitmap> bitmaps) {
if (bitmaps != null && bitmaps.size() > 0) {
if (this.bitmaps.size() > 0) {
if (animator != null) {
animator.cancel();
}
this.bitmaps.clear();
}
this.bitmaps.addAll(bitmaps);
}
return this;
}
public void start() {
invalidate();
}
主要实现类似用ViewPager的Banner功能是在onDraw中,在onDraw中会使用到三个方法,一个是getHandleBitmaps(ArrayList<Bitmap> bitmaps)
,这个是获取确定enterBitmap(下一个进入屏幕的图片)和currentBitmap(当前要显示在屏幕的图片),另一个是drawBitmap(Canvas canvas, float currentBitmapLeft, float enterBitmapLift)
,其中currentBitmapLeft:currentBitmap的绘制起点;enterBitmapLift:enterBitmap的绘制起点,最后一个是startAnimator(float start, float end, long duration, long delayMillis)
,其中start:enterBitmap开始移动的位置;end:enterBitmap最终所停止的位置;duration:动画间隔时间;delayMillis:停留时间,onDraw代码如下:
/**
* 1.代码中的实现核心主要就是确定当前要显示在屏幕中的图片是哪一个,即将进入屏幕的图片是哪一个
* 2.本例中将采用ValueAnimator来实现图片的自动移动以及暂停
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (bitmaps.size() > 0) {
// 画暂停的图片
if (animator == null) {
// 第一次绘制时,绘制currentBitmap在屏幕上
canvas.drawBitmap(bitmaps.get(currentIndex), 0, 0, paint);
} else {
// 如果ValueAnimator是暂停的,则绘制currentBitmap在屏幕上
if (!animator.isRunning()) {
canvas.drawBitmap(bitmaps.get(currentIndex), 0, 0, paint);
}
}
// 获取enterBitmap和currentBitmap
getHandleBitmaps(bitmaps);
/**
* 代码流程
* 判断是否处于手势操作中:
* --->是
* ------>判断手势向左还是向右绘制,调用drawBitmap
* --->否
* ------>animator是否为null或animator是否是运行完毕
* --------->true
* ------------>startAnimator
* --------->false
* ------------>drawBitmap
*
*/
if (mIsBeingDragged) {
// 向左滑动
if (scrollAction == AUTO_SCROLL_TO_LEFT) {
// 向左滑动
drawBitmap(canvas, animatedValue - mViewWidth, animatedValue);
} else if (scrollAction == AUTO_SCROLL_TO_RIGHT) {
// 向右滑动
drawBitmap(canvas, animatedValue, animatedValue - mViewWidth);
}
} else {
if (animator == null) {
if (scrollAction == AUTO_SCROLL_TO_LEFT) {
// 向左滑动
startAnimator(mViewWidth, 0, duration, mDelayMillis);
} else if (scrollAction == AUTO_SCROLL_TO_RIGHT) {
// 向右滑动
startAnimator(0, mViewWidth, duration, mDelayMillis);
}
} else {
if (animator.isRunning()) {
//绘制
// 向左滑动
if (scrollAction == AUTO_SCROLL_TO_LEFT) {
// 向左滑动
drawBitmap(canvas, animatedValue - mViewWidth, animatedValue);
} else if (scrollAction == AUTO_SCROLL_TO_RIGHT) {
// 向右滑动
drawBitmap(canvas, animatedValue, animatedValue - mViewWidth);
}
} else {
if (scrollAction == AUTO_SCROLL_TO_LEFT) {
// 向左滑动
startAnimator(mViewWidth, 0, duration, mDelayMillis);
} else if (scrollAction == AUTO_SCROLL_TO_RIGHT) {
// 向右滑动
startAnimator(0, mViewWidth, duration, mDelayMillis);
}
}
}
}
}
}
private void drawBitmap(Canvas canvas, float currentBitmapLeft, float enterBitmapLift) {
canvas.drawBitmap(currentBitmap, currentBitmapLeft, 0, paint);
canvas.drawBitmap(enterBitmap, enterBitmapLift, 0, paint);
if (BuildConfig.DEBUG) {
canvas.drawText(currentIndex + "", (enterBitmapLift) / 2, mViewHeight / 2, paint);
canvas.drawText(enterIndex + "", (mViewWidth - currentBitmapLeft) / 2, mViewHeight / 2, paint);
}
}
private void getHandleBitmaps(ArrayList<Bitmap> bitmaps) {
if (null != bitmaps && !bitmaps.isEmpty()) {
if (scrollAction == AUTO_SCROLL_TO_LEFT) {
enterIndex = currentIndex + 1;
if (enterIndex >= bitmaps.size()) {
enterIndex = 0;
}
} else if (scrollAction == AUTO_SCROLL_TO_RIGHT) {
enterIndex = currentIndex - 1;
if (enterIndex < 0) {
enterIndex = bitmaps.size() - 1;
}
}
enterBitmap = bitmaps.get(enterIndex);
currentBitmap = bitmaps.get(currentIndex);
}
}
private void startAnimator(float start, float end, long duration, long delayMillis) {
if (animator == null) {
animator = ValueAnimator.ofFloat(start, end);
animator.setDuration(duration);
//animator.setInterpolator(new LinearInterpolator());
animator.setInterpolator(new DecelerateInterpolator());
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {//只要animator.cancel();都会调用此方法
if (!mIsBeingTouch) {
currentIndex = enterIndex;
setPreNextIndex();
/**
* 只有自动动画完毕才会被重置scrollAction
* 当scrollAction==AUTO_SCROLL_TO_RIGHT时,并且再次触发点击事件animator被取消
* 则此处scrollAction不会被赋值,由onTouchEvent中处理
*/
if (scrollAction != defaultScrollAction && isAutoScrollToRight) {
scrollAction = defaultScrollAction;
isAutoScrollToRight = false;
}
invalidate();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
animatedValue = (Float) animation.getAnimatedValue();
invalidate();
}
});
if (!animator.isRunning()) {
animator.setStartDelay(delayMillis);
if (animator.getStartDelay() == mDelayMillis) {
startTime = System.currentTimeMillis();
}
animator.start();
}
} else {//动画取消后再运行,会重置一切animator的参数
if (!animator.isRunning()) {
if (null == propertyValuesHolder) {
propertyValuesHolder = PropertyValuesHolder.ofFloat("x", start, end);
} else {
propertyValuesHolder.setFloatValues(start, end);
}
animator.setValues(propertyValuesHolder);
animator.setDuration(duration);
animator.setStartDelay(delayMillis);// 这一行代码不能再判断条件后执行
if (animator.getStartDelay() == mDelayMillis) {
startTime = System.currentTimeMillis();
}
animator.start();
}
}
}
至此就实现了图片的自动滑动并暂停显示,在上面的代码中startAnimator方法没什么好说的,就是开启ValueAnimator或者对ValueAnimator重新赋值,在ValueAnimator结束后,如果不是处于手势操作状态中会对currentIndex重新赋值并且计算currentIndex的上一个和下一个图片的索引值(preIndex\nextIndex),这两个索引将会用到手势操作中,在onTouchEvent
中主要就是判断手松开后图片应该是向左滑还是向右滑,并且在计算剩余滑动时间和滑动距离或者是剩余等待事件以及修正currentIndex,主要就是一些规则制定以及数值计算的问题,最终实现了Banner轮播控件和手势操作,具体细节请看源码
https://github.com/cqf-hn/BannerView