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开关控件完成~是不是很简单,对,就是这么简单哈。
源码入口
相关文章推荐
- Android设计模式系列-单例模式
- Android设计模式—策略模式
- android动态加载类
- Android设计模式系列-组合模式
- iTV android与客户端加密交互流程
- Cause :android.content.res.Resources$NotFoundException: String resource ID #0x0
- android开发常用基础操作
- Android布局
- Android系统自带样式(@android:style/)
- android学习路线图
- Android源码大放送(实战开发必备)
- Android Touch事件传递机制解析
- Android studio引入百度地图时的bug
- Android开发----音乐播放器(界面设计)
- 关于Android二维码——1.生成二维码
- android Json详解
- Android 获取wifi的加密方式
- [odroid-pc] ubuntu12.04 64bit Android4.0.3 源代码编译报错及解决办法
- Android打包利器Gradle之三板斧
- [odroid-pc] ubuntu12.04 android4.0移植到odroid-pc过程