在开发中遇到一个这样的需求,在选择星期时设计狮想要这个这样的效果:
看到效果,想当然应该是用CheckBox实现吧,选中时背景蓝色文字白色,未选中时背景透明文字黑色。
那最方便的实现方式应该是直接给切图,然而这种用切图,除了因为要做国际化图片量会很大外,另一个原因就是显示效果也不是非常好。
另外一种就给CheckBox定义样式,实现思路应该是下面这个样子(样式代码见底部):
1,设置背景drawable,用
selector.xml来控制它的选中状态
2,文字用/color文件夹下的drawable来实现,设置文字的选择状态的颜色
以上比较瞎扯,最终我是选择用自定义View继承CheckBox实现的,然而遇到的效果是这的:
“一”怎么了,为什么是在上面显示并没有对齐,而且旁边的“日”也没有居中显示。关键代码实现如下:
if (isChecked()) {
// 绘制背景圆
canvas.drawCircle(centerX, centerY, centerX-mPadding, mBgPaint);
// 设置文字画笔颜色
mFontPaint.setColor(mFontCheckColor);
// 获取文字TEXT,以及文字的Rect,来得到文字的高度和宽度
String text = getText().toString();
Rect fontRect = new Rect();
mFontPaint.getTextBounds(text, 0, text.length(), fontRect);
// 设置文字左下角的起始点坐标,绘制文字
canvas.drawText(text, centerX-fontRect.width()/2, centerY+fontRect.height()/2, mFontPaint);
}
由此猜测应该是
getTextBounds()获取文字的RectA与实际的drawText时文字的RectB不是一样的,先看看RectA的大小,
给出三个String,“日”,“一”,“二一”,如下图:
得到如下信息:
1,“日”的Baseline与RectA.bottom基准线不一致,
drawText()方法要设置的Y轴坐标是绘制文本Baseline的坐标,并不是文字最底部的坐标。
2,中间的“一”文本之所以没有显示,就可以在第三个文本“二一”中找到:
从“二一”中可以看出,“一”的底部距离Baseline有比较大的偏差,所以中间的会显示为空白。也就明白了最上面“一”显示偏上的问题。
绘制getTextBounds()获取Text的Rect的代码如下:
@Override
protected void onDraw(Canvas canvas) {
// 设置文字画笔颜色
mFontPaint.setColor(mFontCheckColor);
// 获取文字TEXT,以及文字的Rect,来得到文字的高度和宽度
String text = getText().toString();
Rect fontRect = new Rect();
mFontPaint.getTextBounds(text, 0, text.length(), fontRect);
fontRect.offset(0, -fontRect.top);
canvas.drawText(text, 0, fontRect.bottom, mFontPaint);
Paint linePaint = new Paint();
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setColor(Color.RED);
linePaint.setStrokeWidth(2);
canvas.drawRect(fontRect, linePaint);
}
-----------------------------------------------------------------------------------------------------------------------------------
要想让文本居中显示,使用
getFontMetrics()方法获取要绘制文字的高度。
1,getFontMetrics(),getFontMetricsInt()
getFontMetrics(),getFontMetricsInt()用于返回字符串的测量,而两个方法的区别就是返回值得类型。返回值一共有五个属性:
- Top : baseline到文本顶部的最大的距离
- Ascent : baseline到文本顶部的推荐距离
- Descent : baseline到文本底部的推荐距离
- Bottom : baseline到文本底部的最大距离
- Baseline : 两行文本之间推荐的额外距离,一般为0
在Android的坐标系之中,向下为Y轴正方向,向上位Y轴负方向,所以baseline之上的Top与Ascent都是负数,而baselin之下的Descent、Bottom都是正数。如果要让字符串在垂直方向上居中,则需要在纵坐标上增加Asecent绝对值与descent的差。
2、setTextAlign
setTextAlign可以设置画笔绘制文本的对齐方式,选择center即可完成文本的水平居中。
mPaint.setTextAlign(Paint.Align.CENTER);
3、多行文本居中,代码如下:
private void textCenter(String[] strings, Paint paint, Canvas canvas, Point point,Paint.Align align){
mPaint.setTextAlign(align);
Paint.FontMetrics fontMetrics= mPaint.getFontMetrics();
float top = fontMetrics.top;
float bottom = fontMetrics.bottom;
int length = strings.length;
float total = (length-1)*(-top+bottom)+(-fontMetrics.ascent+fontMetrics.descent);
float offset = total/2-bottom;
for (int i=0; i<length; i++){
float yAxis = -(length-i-1)*(-top+bottom)+offset;
canvas.drawText(strings[i],point.x,point.y+yAxis,mPaint);
}
}
- 1、drawText每次只能绘制一行,所以我们有个for循环
- 2、更具上图,假设字符串数组大于1,每个字符串的高度应该为-top+bottom,总高度就为length*(-top+bottom);
- 3、偏移量就等于length*(-top+bottom)/2-bottom;
- 4、如果顺序向上(即Y负方向)排列的话,那么第i个字符串的高度就是-(length-i-1)*(-top+bottom);
- 5、每个字符串高度的减去偏移量,就应该是每个字符串的baseline的Y坐标
-----------------------------------------------------------------------------------------------------------------------------------
最终实现星期选择效果的代码如下:
public class FontBgCheckBox extends CheckBox {
private Paint mBgPaint;
private Paint mFontPaint;
private int mFontCheckColor, mFontUncheckColor, mBgCheckColor;
private int mPadding;
public FontBgCheckBox(Context context, AttributeSet attrs) {
super(context, attrs);
mFontCheckColor = 0xFFFFFFFF;
mFontUncheckColor = Color.parseColor("#FF616161");
mBgCheckColor = Color.parseColor("#FF50BDE7");
mPadding = dp2px(6, getContext());
mBgPaint = new Paint();
mBgPaint.setColor(mBgCheckColor);
mBgPaint.setAntiAlias(true);
mFontPaint = new Paint();
mFontPaint.setTextSize(dp2px(16, getContext()));
mFontPaint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
if (isChecked()) {
canvas.drawCircle(centerX, centerY, centerX - mPadding, mBgPaint);
mFontPaint.setColor(mFontCheckColor);
String text = getText().toString();
textCenter(new String[]{text}, mFontPaint, canvas, centerX, centerY, Align.CENTER);
} else {
mFontPaint.setColor(mFontUncheckColor);
String text = getText().toString();
textCenter(new String[]{text}, mFontPaint, canvas, centerX, centerY, Align.CENTER);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(widthMeasureSpec, widthMeasureSpec);
}
public static int dp2px(float value, Context context) {
final float scale = context.getResources().getDisplayMetrics().densityDpi;
return (int) (value * (scale / 160) + 0.5f);
}
}
xml布局文件引用示例如下:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<com.ionesmile.testalbum.view.FontBgCheckBox
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checked="true"
android:text="日" />
……
</LinearLayout>
使用样式的方式完成如上效果的代码如下:
<CheckBox
android:id="@+id/Sunday"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sun"
style="@style/checkbox_week_style" />
<style name="checkbox_week_style">
<item name="android:textColor">@drawable/text_color_selector</item>
<item name="android:button">@null</item>
<item name="android:background">@drawable/bg_circle_blue</item>
<item name="android:gravity">center</item>
<item name="android:width">45dp</item>
<item name="android:height">45dp</item>
</style>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#FFFFFFFF" android:state_checked="true"></item>
<item android:color="@color/common_gray"></item>
</selector>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<shape android:shape="oval">
<solid android:color="@color/common_theme" />
<size android:width="45dp" android:height="45dp" />
</shape>
</item>
</selector>