Androd自定义view--简单实战练习

2018-01-26  本文已影响15人  谢尔顿

参考文章:
Android 自定义 View(2) -- 使用

1.渐变的SeekBar

地址:https://github.com/gaojuanjuan/CustomViewDemo
效果图:

渐变的SeekBar
核心代码:
public class GradientSeekBarView extends View {
    private static final int[] SECTION_COLORS = {0xffCC3399, 0xFF00CCFF, 0xff339999};
    private  Context mContext;
    private Paint mPaint;
    private RectF rfBase;       //底层圆角矩形
    private RectF rfCover;      //覆盖层圆角矩形,底层圆角矩形和覆盖层圆角矩形形成一个线框
    private RectF rfContent;    //内容圆角矩形
    private int mWidth;
    private int mHeight;
    private float currentCount;
    private float maxCount;

    public GradientSeekBarView(Context context) {
        this(context,null);
    }

    public GradientSeekBarView(Context context, @Nullable AttributeSet attrs) {
        this(context,attrs,0);
    }

    public GradientSeekBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();
    }

    private void init() {
        mPaint = new Paint();
        rfBase = new RectF();
        rfCover = new RectF();
        rfContent = new RectF();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSize = MeasureSpec.getSize(widthMeasureSpec);
        int hMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSize = MeasureSpec.getSize(heightMeasureSpec);

        if (wMode == MeasureSpec.EXACTLY || wMode ==MeasureSpec.AT_MOST){
            mWidth = wSize;
        }else {
            mWidth = 0;
        }
        if (hMode == MeasureSpec.AT_MOST || hMode ==MeasureSpec.UNSPECIFIED){
            mHeight = dpToPx(15);
        }else {
            mHeight = hSize;
        }
        setMeasuredDimension(mWidth,mHeight);
    }

    private int dpToPx(int dp){
        float scale = mContext.getResources().getDisplayMetrics().density;
        return (int)(dp * scale + 0.5f * (dp >= 0 ? 1:-1));
    }
    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setAntiAlias(true);
        int round = mHeight /2;

        //绘制底层的圆角矩形
        mPaint.setColor(Color.GRAY);
        rfBase.set(0,0,mWidth,mHeight);
        canvas.drawRoundRect(rfBase,round,round,mPaint);

        //绘制内层的圆角矩形,和底层的圆角矩形形成线框
        mPaint.setColor(Color.WHITE);
        rfCover.set(2,2,mWidth-2,mHeight-2);
        canvas.drawRoundRect(rfCover,round,round,mPaint);

        //得到当前位置占总宽度的比例
        float section = currentCount / maxCount;
        
        //创建内容圆角矩形
        rfContent.set(2,2,(mWidth - 2)*section,mHeight - 2);
        
        //如果当前值的比例小于等于1/3,设置颜色为颜色数组中的第一个值
        if (section <= 1.0f/3.0f){
            if (section != 0.0f){
                mPaint.setColor(SECTION_COLORS[0]);                
            }else {
                mPaint.setColor(Color.TRANSPARENT);
            }
        }else{
            int count = (section <= 1.0f / 3.0f * 2) ? 2 : 3;
            int[] colors = new int[count];
            System.arraycopy(SECTION_COLORS,0,colors,0,count);
            LinearGradient shader = new LinearGradient(3, 3, (mWidth - 3) * section, mHeight - 3, colors, null, Shader.TileMode.MIRROR);
            mPaint.setShader(shader);
        }
        canvas.drawRoundRect(rfContent,round,round,mPaint);
        //一次绘制完成之后应该重置一下画笔
        mPaint.reset();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        //下面这句话的作用:表示子组件要自己消费这次事件,告诉父组件不要拦截(抢走)这次的事件
        getParent().requestDisallowInterceptTouchEvent(true);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                moved(x);
                break;
            case MotionEvent.ACTION_MOVE:
                moved(x);
                break;
            case MotionEvent.ACTION_UP:
                moved(x);
                break;
        }
        return true;
    }

    private void moved(float x) {
        if (x > mWidth){
            return;
        }
        currentCount = maxCount * (x / mWidth);
        invalidate();
    }

    public void setMaxCount(float maxCount){
        this.maxCount = maxCount;
    }

    public void setCurrentCount(float currentCount){
        this.currentCount = currentCount > maxCount ? maxCount :currentCount;
        invalidate();
    }

    public float getMaxCount() {
        return maxCount;
    }

    public float getCurrentCount() {
        return currentCount;
    }
}

Activity

        mGradientSeekBar.setMaxCount(100);
        mGradientSeekBar.setCurrentCount(100);

2.自定义闪烁文本的TextView

地址:https://github.com/gaojuanjuan/CustomViewDemo

效果图:


闪烁文本的TextView

核心代码:

public class TwinklingTextView extends TextView {
    private int mViewWidth;                 //用于获取整个View的宽度
    private LinearGradient mLinearGradient;
    private Matrix mGradientMatrix;
    private int mTranslate;                 //用于记录渲染的偏移量

    public TwinklingTextView(Context context) {
        this(context,null);
    }

    public TwinklingTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public TwinklingTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //通过这个条件判断,可以保证只在初始化的时候调用一次
        if (mViewWidth == 0){
            mViewWidth = getMeasuredWidth();
        }
        if (mViewWidth > 0){
            //创建渐变渲染器
            mLinearGradient = new LinearGradient(0,0,mViewWidth,0,new int[]{0x33e20b6c, 0xffe20b6c, 0x33e20b6c},
                    null, Shader.TileMode.CLAMP);
            //对当前view的paing设置渲染
            getPaint().setShader(mLinearGradient);
            mGradientMatrix = new Matrix();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mGradientMatrix != null){
            mTranslate += mViewWidth / 10;
            if (mTranslate > 2*mViewWidth){
                mTranslate = -mViewWidth;
            }
            mGradientMatrix.setTranslate(mTranslate,0);
            mLinearGradient.setLocalMatrix(mGradientMatrix);
            postInvalidateDelayed(50);
        }

    }
}

3.自定义颜色选择器

地址:https://github.com/gaojuanjuan/CustomViewDemo

效果图:


自定义颜色选择器

核心代码:

public class ColorSelectorView extends View {
    private static final int CENTER_RADIUS = 50;
    private int[] mColors;
    private Paint mColorPaint;
    private Paint mCenterPaint;

    private OnColorChangedListener mListener;
    private static final float PI = 3.1415926f;
    private int viewWidth;
    private int viewHeight;

    public ColorSelectorView(Context context) {
        this(context,null);
    }

    public ColorSelectorView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ColorSelectorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mColors = new int[]{0xFFFF0000, 0xFFFF00FF,
                0xFF0000FF, 0xFF00FFFF, 0xFF00FF00,
                0xFFFFFF00, 0xFFFF0000};
        SweepGradient s = new SweepGradient(0, 0, mColors, null);

        //画外部颜色选择圆环
        mColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mColorPaint.setShader(s);
        mColorPaint.setStyle(Paint.Style.STROKE);
        mColorPaint.setStrokeWidth(50);

        //画中心的圆
        mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCenterPaint.setColor(Color.RED);
        mCenterPaint.setStrokeWidth(15);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        viewWidth = w;
        viewHeight = h;
    }

//    @Override
//    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//        setMeasuredDimension(CENTER_X * 2,CENTER_Y * 2);
//    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(viewWidth/2,viewHeight/2);
        canvas.drawCircle(0,0,CENTER_RADIUS * 6,mColorPaint);
        canvas.drawCircle(0,0,CENTER_RADIUS,mCenterPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX() - viewWidth/2;
        float y = event.getY() - viewHeight/2;
        //判断当前触摸的位置是否在圆环内部

        boolean isInRing = Math.sqrt(x * x + y * y) <= (CENTER_RADIUS*6);
        Log.e(Constants.TAG,"onTouchEvent,x = "+x+",y = "+y+",CENTER_RADIUS = "+CENTER_RADIUS+
                ",Math.sqrt(x * x + y * y) = "+Math.sqrt(x * x + y * y));

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                Log.e(Constants.TAG,"ACTION_DOWN");
                if (isInRing){
                    setCenterPaintColor(x,y);
                }
                break;
            case MotionEvent.ACTION_UP:
                if (isInRing&&mListener!=null){
                    mListener.colorChanged(mCenterPaint.getColor());
                }
                invalidate();
                break;

        }
        return true;
    }

    private void setCenterPaintColor(float x, float y) {
        float angle = (float) Math.atan2(y, x);
        //计算得到江都相对于整个圆的比例
        float unit = angle / (2 * PI);
        if (unit < 0){
            unit += 1;
        }
        mCenterPaint.setColor(interpColor(mColors,unit));
        invalidate();
    }

    /**
     * 根据颜色数组和当前触摸点所在的比例计算得到颜色值
     * @param colors    颜色数组
     * @param unit      当前触摸点的比例
     * @return
     */
    private int interpColor(int[] colors, float unit) {
        if (unit <= 0){
            return colors[0];
        }
        if (unit >= 1){
            return colors[colors.length - 1];
        }
        float p = unit * (colors.length -1);
        int i = (int)p;
        p -= i;
        int c0 = colors[i];
        int c1 = colors[i + 1];
        int a = ave(Color.alpha(c0),Color.alpha(c1),p);
        int r = ave(Color.red(c0),Color.red(c1),p);
        int g = ave(Color.green(c0),Color.green(c1),p);
        int b = ave(Color.blue(c0),Color.blue(c1),p);
        return Color.argb(a,r,g,b);
    }

    /**
     * 计算两个值之间的指定比例的值
     * @param s 区域的开始值
     * @param d 区域的结束值
     * @param p 比例
     * @return
     */
    private int ave(int s, int d, float p) {
        return s + Math.round(p * (d-s));
    }

    public interface OnColorChangedListener{
        void colorChanged(int color);
    }

    public void setListener(OnColorChangedListener listener) {
        mListener = listener;
    }
}

4.自定义圆形颜色渐变的SeekBar

地址:https://github.com/gaojuanjuan/CustomViewDemo

效果图:


圆形颜色渐变的SeekBar

核心代码:

public class CircleColorGradientSeekBarView extends View {
    private static final int[] SECTION_COLORS = {0xffffd300, Color.GREEN, 0xff319ed4, 0xffffd300};
    private Paint baseRingPaint;
    private RectF rect;
    private Paint colorfulRingPaint;
    private float ringWidth = 60;
    private int cx;
    private int cy;
    private int outerRadius;
    private float innerRadius;
    private float ringRadius;

    private int angle = 0;//弧度值
    private float mMaxProgress = 100;//最大进度值
    private boolean CALLED_FROM_ANGLE = false;
    private int mProgress;
    private int mProgressPercent;
    private Paint mTextPaint;

    public CircleColorGradientSeekBarView(Context context) {
        this(context, null);
    }

    public CircleColorGradientSeekBarView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleColorGradientSeekBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        baseRingPaint = new Paint();
        baseRingPaint.setColor(Color.GRAY);
        baseRingPaint.setAntiAlias(true);
        baseRingPaint.setStrokeWidth(ringWidth);
        baseRingPaint.setStyle(Paint.Style.STROKE);
        rect = new RectF();

        colorfulRingPaint = new Paint();
        colorfulRingPaint.setColor(Color.parseColor("#ff33b5e5"));
        colorfulRingPaint.setAntiAlias(true);
        colorfulRingPaint.setStrokeWidth(ringWidth);
        colorfulRingPaint.setStyle(Paint.Style.STROKE);

        setBackgroundColor(Color.TRANSPARENT);
        mTextPaint = new TextPaint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setDither(true);
        mTextPaint.setTextSize(100);
        mTextPaint.setColor(Color.RED);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.e(Constants.TAG, "w = " + w + "h = " + h);
        Log.e(Constants.TAG, "width = " + getWidth() + "height = " + getHeight());
        Log.e(Constants.TAG, "MeasuredWidth = " + getMeasuredWidth() + ",MeasuredHeight = " + getMeasuredHeight());
        //选择最小的值作为圆环视图的直径
        int size = (w > h) ? h : w;

        cx = w / 2;
        cy = h / 2;

        outerRadius = size / 2;//圆环外部半径
        ringRadius = outerRadius - ringWidth / 2;//圆环的半径
        innerRadius = outerRadius - ringWidth;//圆环内部半径

        float left = cx - ringRadius;   //渐变圆环外接矩形左边坐标
        float right = cx + ringRadius;  //渐变圆环外接矩形右边坐标
        float top = cy - ringRadius;    //渐变圆环外接矩形顶部坐标
        float bottom = cy + ringRadius; //渐变圆环外接矩形底部坐标
        rect.set(left, top, right, bottom);//设置渐变圆环的位置
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        SweepGradient shader = new SweepGradient(cx, cy, SECTION_COLORS, null);
        Matrix matrix = new Matrix();
        matrix.setRotate(-90, cx, cy);
        shader.setLocalMatrix(matrix);
        colorfulRingPaint.setShader(shader);
        //画背景圆环;
        canvas.drawCircle(cx, cy, ringRadius, baseRingPaint);
        String text = mProgressPercent + "%";
        //获取text的高度和宽度
        Rect textRect = new Rect();
        mTextPaint.getTextBounds(text,0,text.length(),textRect);
        canvas.drawText(text,cx-(textRect.width()/2),cy+(textRect.height()/2),mTextPaint);
        //画渐变圆环,每次刷新界面主要是改变这里的angle的值
        Log.e(Constants.TAG, "onDraw,rect = " + this.rect.toString());
        canvas.drawArc(this.rect, 270, angle, false, colorfulRingPaint);
        super.onDraw(canvas);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        this.getParent().requestDisallowInterceptTouchEvent(true);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                moved(x, y, false);
                break;
            case MotionEvent.ACTION_MOVE:
                moved(x, y, false);
                break;
            case MotionEvent.ACTION_UP:
                moved(x, y, true);
                break;
        }
        return true;
    }

    private void moved(float x, float y, boolean up) {
        //计算圆心到触摸点的直线距离,使用数学中的勾股定理
        float distance = (float) Math.sqrt(Math.pow((x - cx), 2) + Math.pow((y - cy), 2));

        //如果触摸点在外圆半径的一个适配区域内
        if (distance < outerRadius + 100 && distance > innerRadius - 100 && !up) {

            //将角度转换成弧度
            float degrees = (float) ((float) ((Math.toDegrees(Math.atan2(x - cx, cy - y)) + 360.0)) % 360.0);

            //使弧度值永远为正
            if (degrees < 0) {
                degrees += 2 * Math.PI;
            }

            setAngle(Math.round(degrees));
            invalidate();

        } else {
            invalidate();
        }
    }

    public void setAngle(int angle) {
        this.angle = angle;
        float donePercent = (((float) this.angle) / 360) * 100;
        float progress = (donePercent / 100) * getMaxProgress();
        setProgressPercent(Math.round(donePercent));
        CALLED_FROM_ANGLE = true;
        setProgress(Math.round(progress));
    }

    public float getMaxProgress() {
        return mMaxProgress;
    }

    public void setProgress(int progress) {
        mProgress = progress;
    }

    public int getProgressPercent() {
        return mProgressPercent;
    }

    public void setProgressPercent(int progressPercent) {
        mProgressPercent = progressPercent;
    }

    public void setRingBackgroundColor(int color) {
        baseRingPaint.setColor(color);
    }

}

4.自定义折线图

地址:https://github.com/gaojuanjuan/CustomViewDemo

效果图:


折线图

核心代码:


上一篇下一篇

猜你喜欢

热点阅读