项目实战(app应用市场)----重写View

时间:2022-10-04 17:49:18
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;
        }
    }

}