您的位置:首页 > 移动开发 > Android开发

Android自定义View模仿QQ主页的开关

2015-08-01 17:09 537 查看

Android自定义View模仿QQ主页的开关

转载请注明 /article/1462870.html

随着时间的推移,Android的版本不断的更新着。新的观点,新的认知,新的体验也不断的出现。我们作为21世纪的猿,有必要保持一颗探索新知的心,不断摸索、学习、前行。

这是我在CSDN的开篇之作,不求完美,合格就行。好,废话少说,开始主题。

原型

先来看看QQ原型控件是咋样的,直接上图。






看起来其实东西不多哈。呦西,我们先来分析分析~

这个控件可以分成三部分:边框、填充、文字。为啥边框单独拎出来呢?仔细看这张图,会发现它外围包着一层白色的边框。

实现

我们的绘制流程可以是这样:

绘制左边框,包括圆弧和两条线。

填充左边空白部分。

绘制左边的文字。

Canvas旋转一下,继续1、2、3

很简单吧,好了,我们开始代码搞起。

1.属性定义

先在attrs.xml里定义需要用到的属性。这里我们可以定义5个属性,直接上代码

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="selected_color" format="color"/>
    <attr name="no_selected_color" format="color"/>
    <attr name="left_text" format="string"/>
    <attr name="right_text" format="string"/>
    <attr name="angle" format="integer"/>
    <declare-styleable name="QQRoundSwitchX">
        <attr name="selected_color"/>
        <attr name="no_selected_color"/>
        <attr name="left_text"/>
        <attr name="right_text"/>
        <attr name="angle"/>
    </declare-styleable>
</resources>


这些属性分别是:

选择高亮时的颜色

未选择的颜色

左边的文字

右边的文字

圆弧角度

当然你可以再定义一些属性。

2.定义变量,获取属性

继续上代码~

public class QQRoundSwitchX extends View {

    private static final int SELECT_COLOR = 0xffbce6e9;
    private static final int NO_SELECT_COLOR = 0xff1eaacd;

    /* 边框的宽度相对于高的比例 */
    private static final float BOARD_WIDTH_RATE = 1F/20F;
    /* 字体大小相对于高度的比例 */
    private static final float TEXT_SIZE_RATE = 3F/7F;
    /* 宽相对于高的比例 */
    private static final float WIDTH_RATE = 4;

    private Paint mBoardPaint;
    private TextPaint mTextPaint;
    private Paint mFillPaint;

    private int angle;
    private int selectColor;
    private int noSelectColor;
    private String leftText;
    private String rightText;

    private float mWidth;
    private float mHeight;

    private float mBoardWidth;// mBoardWidth = mHeight*BOARD_WIDTH_RATE
    private float mRadius;
    /* 圆弧到长方形边框的距离 */
    private float mArcToBoard;
    /* 中间长方形的长*/
    private float mRectWidth;

    private boolean leftSelected;

    private RectF boardArcRectF;
    private RectF arcRectF;
    private RectF rectRectF;

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

    public QQRoundSwitchX(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.QQRoundSwitchX);
        selectColor = ta.getColor(R.styleable.QQRoundSwitchX_selected_color, SELECT_COLOR);
        noSelectColor = ta.getColor(R.styleable.QQRoundSwitchX_no_selected_color, NO_SELECT_COLOR);
        leftText = ta.getString(R.styleable.QQRoundSwitchX_left_text);
        rightText = ta.getString(R.styleable.QQRoundSwitchX_right_text);
        angle = ta.getInteger(R.styleable.QQRoundSwitchX_angle, 180);
        ta.recycle();

        initPaints();

        boardArcRectF = new RectF();
        arcRectF = new RectF();
        rectRectF = new RectF();
    }

    private void initPaints() {
        mBoardPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBoardPaint.setStyle(Paint.Style.STROKE);
        mBoardPaint.setColor(selectColor);

        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextAlign(Paint.Align.CENTER);

        mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mFillPaint.setStyle(Paint.Style.FILL);
    }


这部分代码很简单,大概说一下。控件继承自View,这没有异议。然后定义了一下比例值。这里有必要强调一下,在自定义view里,尽量使用比例来计算各个尺寸,这样一来,复用性会更强一点。

然后定义了三个画笔,分别用来画边框、填充和文字。然后定义了一堆RectF,这是为了在低端版本上使用。API21开始画圆弧才可以拆开来赋值(left,top,right,bottom)。

然后在初始化里获取到定义的属性,并初始化画笔。这里边框的颜色是确定的,就直接定义为selectColor,即偏白的颜色。

3.计算控件长宽,计算半径、边框等尺寸

我们先来计算控件长宽,贴码

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);

        if (mHeight*WIDTH_RATE <= mWidth) {
            mWidth = mHeight*WIDTH_RATE;
        }else {
            mHeight = mWidth/WIDTH_RATE;
        }
        int width = (int)mWidth;
        int height = (int)mHeight;
        setMeasuredDimension(width, height);
        mWidth = width;
        mHeight = height;
    }


onMeasure是用来测量该控件的占据尺寸的。这里,默认定义宽是高的4倍。所以,先获取父类给予的最大宽度mWidth和最大高度mHeight,通过MeasureSpec.getSize()这个方法。再判断,如果高的4倍比宽短,那么以高为基准,调整下宽的长度;如果高的4倍比宽长,那么以宽为基准,调整下高的长度。然后通知给父类,通过setMeasuredDimension((int)mWidth, (int)mHeight);

接着,我们来看下各个尺寸怎么计算。因为我这里定义圆弧角度是可变的(考虑范围90~180),所以要稍微计算下尺寸。

这里,我简单粗暴地用windows自带的画板画了一张图~



上图标记红色的这条线,我在代码里定义为mArcToBoard。

从图可以计算得:

圆弧半径=mHeight/2/sin(radian/2), radian为弧度,角度换算下就行。

mArcToBoard=圆弧半径-圆弧半径*cos(radian/2)

还有几个值我就不写,类似的计算。所以,贴码。

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

        mBoardWidth = mHeight*BOARD_WIDTH_RATE;
        mTextPaint.setTextSize(mHeight*TEXT_SIZE_RATE);
        mBoardPaint.setStrokeWidth(mBoardWidth);

        double radian = angle*1f/180*Math.PI/2;
        mRadius = (float) (mHeight/2/Math.sin(radian));
        mArcToBoard = (float) (mRadius - mRadius*Math.cos(radian));
        mRectWidth = mWidth - mArcToBoard*2;

        float halfBoardWidth = mBoardWidth*1f/2;

        arcRectF.left = mBoardWidth;
        arcRectF.right = mRadius*2-mBoardWidth;
        arcRectF.top = -(mRadius-mHeight/2-mBoardWidth);
        arcRectF.bottom = mHeight-arcRectF.top;

        boardArcRectF.left = halfBoardWidth;
        boardArcRectF.right = mRadius*2-halfBoardWidth;
        boardArcRectF.top = -(mRadius-mHeight/2-halfBoardWidth);
        boardArcRectF.bottom = mHeight-arcRectF.top+halfBoardWidth;

        rectRectF.left = mArcToBoard-1;
        rectRectF.right = mWidth/2+1;
        rectRectF.top = mBoardWidth-1;
        rectRectF.bottom = mHeight-mBoardWidth+1;
    }


onSizeChanged()这里不详细讲解了,你就记住,在onMeasure之后,它可能会被调用。这里为什么说可能呢?因为只有控件发生变化的时候会被调用,包括第一次创建的时候。所以这里是肯定会被调到的,因为是头一次创建。

在里面,我们计算了圆弧半径、边框长度等值,然后给各个RectF赋值。代码很清晰,就不详细讲了。

4.开始绘制

在刚开始的地方,我们已经分析过了绘制流程,这里再啰嗦一下。

先绘制左边,分三步:

1.先绘制左边的边框,包括圆弧和上下两条线。

2.再填充左边的空白部分,包括圆弧填充和长方形填充。

3.最后,写字。

然后绘制右边,我们通过canvas的层次特性和画布的可旋转特性,这样就可以很简单绘制右边。因为左右画的内容其实是一样的,只是颜色不一样,那么就先调整canvas,然后继续画左边就可以了。如果不是很懂,没事,上代码,写得很清晰~

@Override
    protected void onDraw(Canvas canvas) {
        // 先画左边
        drawOneSide(canvas, leftSelected, true);
        canvas.save();
        canvas.rotate(180, mWidth/2, mHeight/2);
        drawOneSide(canvas, !leftSelected, false);
        canvas.restore();
    }

    private void drawOneSide(Canvas canvas, boolean isSelected, boolean isLeft) {
        // 先画边框
        // 先画圆弧边框
        canvas.drawArc(boardArcRectF, 180-angle/2, angle, false, mBoardPaint);
        // 再画上下两条横线
        canvas.drawLine(mArcToBoard, mBoardWidth/2, mWidth/2, mBoardWidth/2, mBoardPaint);
        canvas.drawLine(mArcToBoard, mHeight-mBoardWidth/2, mWidth/2, mHeight-mBoardWidth/2, mBoardPaint);
        // 再填充内部颜色
        // 先填充圆弧
        if (isSelected) {
            mFillPaint.setColor(selectColor);
        }else {
            mFillPaint.setColor(noSelectColor);
        }
        canvas.drawArc(arcRectF, 180-angle*1f/2, angle, false, mFillPaint);
        // 再填充方形
        canvas.drawRect(rectRectF, mFillPaint);

        // 最后写字
        String text;
        if (isLeft) {
            text = leftText;
        }else {
            canvas.save();
            canvas.rotate(180, mWidth/4, mHeight/2);
            text = rightText;
        }
        if (isSelected) {
            mTextPaint.setColor(noSelectColor);
        }else {
            mTextPaint.setColor(selectColor);
        }
        canvas.drawText(text,
                mWidth/4,
                mHeight/2-(mTextPaint.descent()+mTextPaint.ascent())/2,
                mTextPaint);
        if (!isLeft) {
            canvas.restore();
        }
    }


大概讲一下,先画左边,画完之后,我们先保存画布画好的情况,用到canvas.save();然后把画布按控件的中心点为旋转中心点,旋转180度,这样一来画布已经旋转,右边在左边了,可以直接按照左边的方式画了。

这里要讲一下文字的绘制。如果在文字绘制那里不做画布处理,那么右边的文字会是倒过来的情况,有兴趣你们可以自己调整下代码尝试下。所以,那里还是要进行旋转,按照同样的处理方式保存画布,调整画布,绘制文字,再还原画布就可以了。

至此,绘制完成~

贴个图看看



啥?这不是QQ本身吗?看仔细喽~这是我山寨的(纯属无聊练练手)。list里的第一个item的图仔细看看,我没有做到像QQ那样的不符合空间规律,有QQ的童鞋可以打开QQ仔细瞅瞅,没有的也没事,我贴个图~






左边的一张是我山寨的QQ,右边是正版QQ,有看到区别了吧。QQ就像是卡片按逆时针互相卡起来了,而我的是按顺时针往上叠。

咳咳~,扯多了,回到正题,绘制已经完成了,效果图也看到了。至于其他角度,你们可以自己去调整试试。

STOP!还少了啥!对了,还少了点击事件。这个其实很简单啦,我也没写,就复写下onTouchEvent(),然后监听下ACTION_DOWN和ACTION_UP,计算下点击的位置,确定点击的是左边还是右边,然后调整下leftSelected这个参数。

QQ里面是按下的时候也是高亮的,那就再加个boolean变量,然后在onDraw里调整下就行了。

至此,山寨QQ开关控件完成~是不是很简单,对,就是这么简单哈。

源码入口
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: