Android自定義輪播圖指示器
輪播圖在項目中一般會使用VeiwPager來實現,同時還會關聯輪播指示器。
輪播指示器效果gif
改造ViewPager的OnPageChangedListener,添加自定義的ViewPager滾動監聽器(直接將上一篇文章的代碼貼過來了)
/**
* ViewPager輔助類
*/
public class ViewPagerHelper implements ViewPager.OnPageChangeListener {
private double mLastPositionOffsetSum; // 上一次滑動總的偏移量
private OnPageScrollListener mOnPageScrollListener;
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// 當前總的偏移量
float currentPositionOffsetSum = position + positionOffset;
// 上次滑動的總偏移量大于此次滑動的總偏移量,頁面從右向左進入(手指從右向左滑動)
boolean rightToLeft = mLastPositionOffsetSum <= currentPositionOffsetSum;
if (currentPositionOffsetSum == mLastPositionOffsetSum) return;
int enterPosition;
int leavePosition;
float percent;
if (rightToLeft) { // 從右向左滑
enterPosition = (positionOffset == 0.0f) ? position : position + 1;
leavePosition = enterPosition - 1;
percent = (positionOffset == 0.0f) ? 1.0f : positionOffset;
} else { // 從左向右滑
enterPosition = position;
leavePosition = position + 1;
percent = 1 - positionOffset;
}
if (mOnPageScrollListener != null) {
mOnPageScrollListener.onPageScroll(enterPosition, leavePosition, percent);
}
mLastPositionOffsetSum = currentPositionOffsetSum;
}
@Override
public void onPageSelected(int position) {
if (mOnPageScrollListener != null) {
mOnPageScrollListener.onPageSelected(position);
}
}
/**
* @param state 當前滑動狀態
* ViewPager.SCROLL_STATE_IDLE 頁面處于閑置、穩定狀態,即沒被拖動也沒慣性滑動
* ViewPager.SCROLL_STATE_DRAGGING 頁面正在被用戶拖動,即手指正在拖動狀態
* Viewpager.SCROLL_STATE_SETTLING 頁面處于即將到達最終狀態的過程,即手指松開后慣性滑動狀態
*/
@Override
public void onPageScrollStateChanged(int state) {
if (mOnPageScrollListener != null) {
mOnPageScrollListener.onPageScrollStateChanged(state);
}
}
public void bindScrollListener(ViewPager viewPager, OnPageScrollListener onPageScrollListener) {
mOnPageScrollListener = onPageScrollListener;
viewPager.addOnPageChangeListener(this);
}
}
/**
* ViewPage的頁面滾動監聽器
*/
public interface OnPageScrollListener {
/**
* 頁面滾動時調用
*
* @param enterPosition 進入頁面的位置
* @param leavePosition 離開的頁面的位置
* @param percent 滑動百分比
*/
void onPageScroll(int enterPosition, int leavePosition, float percent);
/**
* 頁面選中時調用
*
* @param position 選中頁面的位置
*/
void onPageSelected(int position);
/**
* 頁面滾動狀態變化時調用
*
* @param state 頁面的滾動狀態
*/
void onPageScrollStateChanged(int state);
}
根據兩種指示器的效果分析,都是通過回調onPageScroll方法中不斷變化的enterPositon、leavePosition和percent來實現。
NumberIndicator(數字指示器)
當ViewPager頁面從右向左滑動時,指示器中對應頁面的數字從下往上滾動,頁面停止,數字停止在中間位置。
當ViewPager頁面從左向右滑動時,指示器中對應頁面的數字從下往上滾動,頁面停止,數字停止在中間位置。
最終指示器效果可以采用繪制View的方式來實現(當然也可以采用組合控件的方式來實現)。ViewPager頁面滑動時,不斷重繪View,達到指示數字上下滑動的效果。
/**
* 數字指示器
*/
public class NumberIndicater extends View implements OnPageScrollListener {
private Context mContext;
private int mCircleColor;
private int mCircleSize;
private int mNumberColor;
private int mNumberSize;
private int mCount;
private int mCurrent;
private Paint mCirclePaint;
private Paint mTextPaint;
private float offset; // 頁面偏移百分比
private boolean isUp; // 指示器數字是否向上滑動
public NumberIndicater(Context context) {
this(context, null);
}
public NumberIndicater(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NumberIndicater(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
initPaint();
}
/**
* 初始化屬性
*
* @param context
* @param attrs
*/
private void initAttrs(Context context, AttributeSet attrs) {
mContext = context;
mCircleSize = dp2px(48f);
mCircleColor = 0xfffdd63b;
mNumberSize = sp2px(14f);
mNumberColor = 0xff353535;
// 自定義屬性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NumberIndicater);
mCircleColor = ta.getColor(R.styleable.NumberIndicater_circle_color, mCircleColor);
mCircleSize = (int) ta.getDimension(R.styleable.NumberIndicater_circle_size, mCircleSize);
mNumberColor = ta.getColor(R.styleable.NumberIndicater_number_color, mNumberColor);
mNumberSize = (int) ta.getDimension(R.styleable.NumberIndicater_number_size, mNumberSize);
ta.recycle();
}
private void initPaint() {
mCirclePaint = new Paint();
mCirclePaint.setAntiAlias(true);
mCirclePaint.setColor(mCircleColor);
mCirclePaint.setStyle(Paint.Style.FILL_AND_STROKE);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextAlign(Paint.Align.LEFT);
mTextPaint.setColor(mNumberColor);
mTextPaint.setTextSize(mNumberSize);
offset = 1;
mCount = 3;
mCurrent = 1;
isUp = true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 設置測量后的尺寸
setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));
}
private int measure(int measureSpec) {
int size = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.EXACTLY:
size = specSize;
break;
case MeasureSpec.AT_MOST:
size = mCircleSize;
break;
}
return size;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 繪制圓形底圖
canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, mCircleSize / 2f, mCirclePaint);
// 繪制分割線
drawSplit(canvas);
// 繪制右邊總數數字
drawTotleNumber(canvas);
// 繪制左邊指示數字
drawIndicatNumber(canvas);
}
private void drawSplit(Canvas canvas) {
String text = "/";
float width = mTextPaint.measureText(text);
float x = (getWidth() - width) / 2f;
Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
float y = getHeight() / 2f + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2f;
// x為繪制文本左邊緣距離X軸的距離,y為繪制文本基線距離Y軸的位置
canvas.drawText(text, x, y, mTextPaint);
}
private void drawTotleNumber(Canvas canvas) {
String text = String.valueOf(mCount);
float x = getWidth() / 2f + mTextPaint.measureText("/") / 2f + 3;
Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
float y = getHeight() / 2f + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2f;
canvas.drawText(text, x, y, mTextPaint);
}
private void drawIndicatNumber(Canvas canvas) {
mTextPaint.setTextSize(mNumberSize * 1.3f);
String text = String.valueOf(mCurrent);
Rect rect = new Rect();
// 獲取文本的寬度
float width = mTextPaint.measureText(text);
// 獲取文本的高度
mTextPaint.getTextBounds(text, 0, text.length(), rect);
float height = rect.height();
// 文本左邊緣距離X軸的距離
float x = getWidth() / 2f - mTextPaint.measureText("/") / 2f - 3 - width;
Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
// 文本基線位置距離Y軸的距離
float y = getHeight() / 2f + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2f;
if (isUp) { // 指示數字向上滑動
y = offset * y + (1 - offset) * (getHeight() / 2f - mCircleSize / 2f + mCircleSize + height);
} else { // 指示數字向下滑動
y = offset * y + (1 - offset) * (getHeight() / 2f - mCircleSize / 2f);
}
canvas.drawText(text, x, y, mTextPaint);
mTextPaint.setTextSize(mNumberSize);
}
/**
* 將指示器綁定到ViewPager
*
* @param viewPager view pager
*/
public void bindViewPager(ViewPager viewPager) {
if (viewPager != null && viewPager.getAdapter() != null) {
mCount = viewPager.getAdapter().getCount();
new ViewPagerHelper().bindScrollListener(viewPager, this);
invalidate(); // 綁定ViewPager后指示器重繪,因為指示器的數字與初始的可能不同
}
}
@Override
public void onPageScroll(int enterPosition, int leavePosition, float percent) {
mCurrent = enterPosition + 1;
offset = percent;
isUp = enterPosition > leavePosition;
postInvalidate(); // 滑動過程中不斷重繪
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
private int dp2px(float dpValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpValue,
mContext.getResources().getDisplayMetrics());
}
private int sp2px(float dpValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
dpValue,
mContext.getResources().getDisplayMetrics());
}
}
PointIndicator(圓點指示器)
根據ViewPager滑動的位置和百分比,動態繪制指示小圓點。通過Viewpger滑動過程中的enterPosition和leavePosition以及滑動百分比percent來計算出滑動小圓點的左邊位置即可。
/**
* 圓點指示器
*/
public class PointIndicator extends View implements OnPageScrollListener {
private Context mContext;
private int mNormalColor;
private int mSelectColor;
private int mPointSize;
private int mPointSpace;
private Paint mNormalPaint;
private Paint mSelectPaint;
private int mCount;
private int enterPosition;
private int leavePosition;
private float percent;
public PointIndicator(Context context) {
this(context, null);
}
public PointIndicator(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PointIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
initPaint();
}
private void initPaint() {
mNormalPaint = new Paint();
mNormalPaint.setColor(mNormalColor);
mNormalPaint.setAntiAlias(true);
mSelectPaint = new Paint();
mSelectPaint.setColor(mSelectColor);
mSelectPaint.setAntiAlias(true);
mCount = 4;
}
private void initAttrs(Context context, AttributeSet attrs) {
mContext = context;
mNormalColor = 0x66cccccc;
mSelectColor = 0xfffdd63b;
mPointSize = dp2px(3f);
mPointSpace = dp2px(3f);
// 自定義屬性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PointIndicator);
mNormalColor = ta.getColor(R.styleable.PointIndicator_normal_color, mNormalColor);
mSelectColor = ta.getColor(R.styleable.PointIndicator_select_color, mSelectColor);
mPointSize = (int) ta.getDimension(R.styleable.PointIndicator_point_size, mPointSize);
mPointSpace = (int) ta.getDimension(R.styleable.PointIndicator_point_space, mPointSpace);
ta.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureWidth(int measureSpec) {
int size = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.EXACTLY:
size = specSize;
break;
case MeasureSpec.AT_MOST:
size = mCount * mPointSize + (mCount - 1) * mPointSpace;
break;
}
return size;
}
private int measureHeight(int measureSpec) {
int size = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.EXACTLY:
size = specSize;
break;
case MeasureSpec.AT_MOST:
size = mPointSize;
break;
}
return size;
}
@Override
protected void onDraw(Canvas canvas) {
// 繪制normalPoint
drawNormalPoint(canvas);
// 繪制selectPoint
drawSelectPoint(canvas);
}
private void drawSelectPoint(Canvas canvas) {
float cx;
if (enterPosition > leavePosition) {
cx = (leavePosition + 0.5f) * mPointSize
+ leavePosition * mPointSpace
+ (mPointSize + mPointSpace) * percent;
} else {
cx = (leavePosition + 0.5f) * mPointSize
+ leavePosition * mPointSpace
- (mPointSize + mPointSpace) * percent;
}
float cy = getHeight() / 2;
float radius = mPointSize / 2f;
canvas.drawCircle(cx, cy, radius, mSelectPaint);
}
private void drawNormalPoint(Canvas canvas) {
for (int i = 0; i < mCount; i++) {
float cx = mPointSize / 2f + (mPointSize + mPointSpace) * i;
float cy = getHeight() / 2;
float radius = mPointSize / 2f;
canvas.drawCircle(cx, cy, radius, mNormalPaint);
}
}
public void bindViewPager(ViewPager viewPager) {
if (viewPager != null) {
if (viewPager.getAdapter() != null) {
mCount = viewPager.getAdapter().getCount();
new ViewPagerHelper().bindScrollListener(viewPager, this);
requestLayout(); // 綁定ViewPager后指示器重新布局,因為指示器的數量和寬度可能有變化
}
}
}
@Override
public void onPageScroll(int enterPosition, int leavePosition, float percent) {
this.enterPosition = enterPosition;
this.leavePosition = leavePosition;
this.percent = percent;
postInvalidate();
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
private int dp2px(float dpValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpValue,
mContext.getResources().getDisplayMetrics());
}
}
涉及到的自定義屬性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NumberIndicater">
<attr name="circle_color" format="color"/>
<attr name="circle_size" format="dimension"/>
<attr name="number_size" format="dimension"/>
<attr name="number_color" format="color"/>
</declare-styleable>
<declare-styleable name="PointIndicator">
<attr name="point_size" format="dimension"/>
<attr name="point_space" format="dimension"/>
<attr name="normal_color" format="color"/>
<attr name="select_color" format="color"/>
</declare-styleable>
</resources>
以上兩種指示器都是通過自繪View的方式來實現。通過自定義的OnPageScrollListener還可以實現更多效果炫酷的指示器。
來自:http://www.jianshu.com/p/60be684d897d
本文由用戶 batmanlf 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!