import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.view.View; import com.example.googleplay.utils.UIUtils; public class IndicatorView extends View { private static final int POSITION_NONE = -1; private Drawable mDrbIndicator; private int mCount; private int mSelection; private int mInterval; public IndicatorView(Context context) { super(context); init(); } /** 初始化 */ private void init() { mSelection = POSITION_NONE; } /** 设置数目 */ public void setCount(int count) { count = count > 0 ? count : 0; if (count != mCount) { mCount = count; requestLayoutInner(); requestInvalidate(); } } /** 设置选中项 */ public void setSelection(int selection) { if (selection != mSelection) { mSelection = selection; requestInvalidate(); } } /** 设置选中项的图片 */ public void setIndicatorDrawable(Drawable drawable) { mDrbIndicator = drawable; requestLayoutInner(); requestInvalidate(); } /** 设置item之间间隔 */ public void setInterval(int interval) { if (interval != mInterval) { mInterval = interval; requestLayoutInner(); requestInvalidate(); } } /** * invalidate:View本身调用迫使view重画。 */ private void requestInvalidate() { if (UIUtils.isRunInMainThread()) { invalidate(); } else { postInvalidate(); } } /** * requestLayout:当view确定自身已经不再适合现有的区域时, 该view本身调用这个方法要求parent * view重新调用他的onMeasure onLayout来对重新设置自己位置。 * 特别的当view的layoutparameter发生改变,并且它的值还没能应用到view上,这时候适合调用这个方法。 */ private void requestLayoutInner() { UIUtils.runInMainThread(new Runnable() { @Override public void run() { requestLayout(); } }); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width; int height; // 而是将模式和尺寸组合在一起的数值 int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); // 计算高度 /* * MeasureSpec.EXACTLY是精确尺寸,当我们将控件的 * layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip", * 或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。 */ if (widthMode == MeasureSpec.EXACTLY) {// 如果是精确的,就采用精确值 width = widthSize; } else {// 否则就采用图片的宽度 获取资源文件中图片的大小,最简单的最直接的方法 int indicatorW = mDrbIndicator == null ? 0 : mDrbIndicator.getIntrinsicWidth(); int expectedW = indicatorW * mCount + mInterval * (mCount - 1) + getPaddingLeft() + getPaddingRight(); if (widthMode == MeasureSpec.AT_MOST) { /** * MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为 * WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过 * 父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。 */ width = Math.min(expectedW, widthSize); } else { width = expectedW; } } // 计算高度 if (heightMode == MeasureSpec.EXACTLY) { height = heightSize; } else {// 否则就采用图片的高度 int indicatorH = mDrbIndicator == null ? 0 : mDrbIndicator.getIntrinsicHeight(); int expectedH = indicatorH + getPaddingTop() + getPaddingBottom(); if (expectedH == MeasureSpec.AT_MOST) { height = Math.min(expectedH, heightSize); } else { height = expectedH; } } // 继承View,实现自己想要的组件,那么需要使用到setMeasuredDimension这个方法,这个方法决定了当前View的大小 setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mDrbIndicator == null || mCount == 0) { return; } int w = mDrbIndicator.getIntrinsicWidth(); int h = mDrbIndicator.getIntrinsicHeight(); int horizontalSideSpacing = (getWidth() - getPaddingLeft() - w * mCount - mInterval * (mCount - 1)) / 2; int verticalSideSpacing = (getHeight() - getPaddingTop() - getPaddingBottom() - h) / 2; int l = getPaddingLeft() + horizontalSideSpacing; int t = getPaddingTop() + verticalSideSpacing; int rEdge = getRight() - getPaddingRight(); int bEdge = getBottom() - getPaddingBottom(); // 计算出间隙和范围,然后画图片,实际画的是同一张图片,只是改变图片的bounds for (int i = 0; i < mCount; i++) { mDrbIndicator.setBounds(l, t, Math.min(1 + w, rEdge), Math.min(t + h, bEdge)); if (i == mSelection) { mDrbIndicator.setState(new int[] { android.R.attr.state_selected }); } else { mDrbIndicator.setState(null); } mDrbIndicator.draw(canvas); l += w + mInterval; } } }