本文将介绍一种有效改变Android按钮颜色的方法。
按钮可以在状态改变时改变其颜色(例如按下,禁用,高亮显示)。但是,这需要一一说明每个状态。这篇文章将提供你一个根据状态变化轻松改变按钮颜色的方法。如果你正在写自定义视图,那么不妨也来读一读,因为中间我会涉及到如何用自定义属性实现自定义视图的相关内容。
如何实现
Android提供了灵活的绘制选择机制,可根据视图状态转变视图外观。每个状态通过一个单独的部分而存在。例如:在正常、禁用、按下、高亮状态下的按钮有着不同的背景颜色。请看下面的代码示例:
button_1_background.xml
1
2
3
4
5
6
7
8
|
<selector xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<!— pressed state -->
<item android:drawable=
"@drawable/button_1_selected"
android:state_pressed=
"true"
/>
<!-- focused state -->
<item android:drawable=
"@drawable/button_1_focused"
android:state_focused=
"true"
/>
<!--
default
state -->
<item android:drawable=
"@drawable/button_1_normal"
/>
</selector>
|
每个状态drawables的属性(button_1_selected, button_1_focused,button_1_normal)必须定义在相应的在drawables目录下:
button_1_normal.xml
1
2
3
4
|
<shape xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<solid android:color=
"@color/button_1_normal_background"
/>
<corners android:radius=
"10dip"
/>
</shape>
|
button_1_focused.xml
1
2
3
4
|
<shape xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<solid android:color=
"@color/button_1_focused_background"
/>
<corners android:radius=
"10dip"
/>
</shape>
|
button_1_selected.xml
1
2
3
4
|
<shape xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<solid android:color=
"@color/button_1_selected_background"
/>
<corners android:radius=
"10dip"
/>
</shape>
|
然后设置按钮背景:
1
|
android:background=
"@drawable/button_1_background"
|
这种方法非常灵活。但是,当你的app有许多按钮,而每个按钮的颜色又各不相同时,维护每个按钮的上述所有XML文件就会变得异常困难起来。如果你改变正常状态的按钮颜色,那么你必须改变其他状态的颜色。在上面的例子中,每个按钮需要4个XML文件。那么如果你的应用程序有10个或更多个按钮呢?
为了清楚说明我的意思,请看下面的截图:
这些截图来自于一款免费产品BMEX。
这两张图片分别是app的主屏幕和发送屏幕。两个屏幕都采用了Metro风格。每个屏幕都有6个不同颜色的按钮。并且按钮的颜色会根据状态的改变而改变。共计12个按钮,所以我们需要12个drawable selector XML文件和24个drawable state XML文件。并且随着app的发展,软件还得允许新的屏幕和新的按钮的添加。维护这些内容可不是一项简单的任务。
为了使过程更加简单和高效,我们另寻了一种更有效的解决方案——并且已经实现在自定义按钮视图中。这是一个容易初始化的按钮。我们称之为RoundButton,因为它支持圆角。
在另一个产品中,我们需要高亮功能,但是,又不想因此单独创建自定义视图。所以,我们把它添加到RoundButton中。请看下面的截图:
正如你所见,我们可以选择也可以不选屏幕上的按钮(顶部的列表图表和每个元素后面的添加图标)。当按钮被选中后,它的highlighted状态就被设置为true,反之,则为false。并且按钮的外观会作适当改变。在上面的例子中,高亮模式使用了“image”。在这种模式下,图像的可见象素会被绘制为高亮颜色。
首先,我们为RoundButton定义属性集。这是一组可以通过布局XML设置的属性。
attrs_round_button.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<resources>
<declare-styleable name=
"RoundButton"
>
<attr name=
"image"
format=
"reference"
/>
<attr name=
"bgcolor"
format=
"color"
/>
<attr name=
"text"
format=
"string"
/>
<attr name=
"radius"
format=
"float"
/>
<attr name=
"highlightColor"
format=
"color"
/>
<attr name=
"highlightMode"
format=
"enum"
>
<
enum
name=
"none"
value=
"0"
/>
<
enum
name=
"image"
value=
"1"
/>
<
enum
name=
"background"
value=
"2"
/>
</attr>
</declare-styleable>
</resources>
|
我们增加了 image,bgcolor,text,边框圆角半径,highlightColor和highlightMode属性。按下状态的颜色会从bgcolor导出(后面会描述的)。
实现按钮
首先,我们需要实现构造函数和解析参数。我们创建了3个不同的构造函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public
RoundButton(Context context, AttributeSet attrs,
int
defStyle) {
super
(context, attrs, defStyle);
init(attrs, defStyle);
}
public
RoundButton(Context context, AttributeSet attrs) {
super
(context, attrs);
init(attrs,
0
);
}
public
RoundButton(Context context) {
super
(context);
init(
null
,
0
);
}
|
所有这些构造函数调用init方法。
现在,我们需要实现init方法。它将属性集和默认样式作为输入参数。在init方法中,我们获取属性值,并初始化内部变量。如果属性集为null,那就使用默认值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
private
void
init(AttributeSet attrs,
int
defStyle) {
Drawable image;
int
bgcolor;
String text;
if
(attrs !=
null
) {
final
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.RoundButton, defStyle,
0
);
image = a.getDrawable(R.styleable.RoundButton_image);
bgcolor = a.getColor(R.styleable.RoundButton_bgcolor,
0xffffffff
);
text = a.getString(R.styleable.RoundButton_text);
radius = a.getFloat(R.styleable.RoundButton_radius,
12
.0f);
highlightMode = HighlightMode.getValue(a.getInt
(R.styleable.RoundButton_highlightMode, HighlightMode.None.ordinal()));
highlightColor = a.getColor(R.styleable.RoundButton_highlightColor,
0xff00b5ff
);
a.recycle();
}
else
{
image =
null
;
text =
""
;
bgcolor =
0xff808080
;
radius =
12
.0f;
highlightMode = HighlightMode.None;
highlightColor =
0xff00b5ff
;
}
init(image, bgcolor, text);
}
|
然后,我们创建另一个init方法。这个方法用于创建对象,并需要渲染按钮的内容。 此处的init方法声明为public,因为创建RoundButton时需要调用它。它创建了背景和按下时的“喷漆(paint)”——绘制正常和按下状态时的背景的对象。按下的颜色选取比bgcolor更亮的颜色。使颜色变亮的的方法,稍后会进行说明。这里初始化了高亮模式。如果背景设置为高亮,那就创建高亮喷漆,用于绘制高亮时的按钮背景。如果图像模式设置为高亮,那就创建高亮图像。在createHighlightImage方法中创建图像的代码,之后会一一给出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public
void
init(Drawable image,
int
bgcolor, String text) {
this
.image = image;
bgpaint =
new
Paint(Paint.ANTI_ALIAS_FLAG);
bgpaint.setColor(bgcolor);
pressedBgpaint =
new
Paint(Paint.ANTI_ALIAS_FLAG);
pressedBgpaint.setColor(brighter(bgcolor));
if
(text ==
null
)
text =
""
;
this
.text = text;
textPaint =
new
Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(
0xffffffff
);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(pixelsToSp(getContext(), textSize));
if
(highlightMode == HighlightMode.Background) {
highlightPaint =
new
Paint(Paint.ANTI_ALIAS_FLAG);
highlightPaint.setColor(highlightColor);
}
else
if
(highlightMode == HighlightMode.Image) {
highlightImage = createHighlightImage();
}
setClickable(
true
);
}
|
要获得按下状态的色值,我们创建了brighter方法。它将颜色作为参数,并返回比该颜色更亮的颜色。这个方法也很简单:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public
void
init(Drawable image,
int
bgcolor, String text) {
this
.image = image;
bgpaint =
new
Paint(Paint.ANTI_ALIAS_FLAG);
bgpaint.setColor(bgcolor);
pressedBgpaint =
new
Paint(Paint.ANTI_ALIAS_FLAG);
pressedBgpaint.setColor(brighter(bgcolor));
if
(text ==
null
)
text =
""
;
this
.text = text;
textPaint =
new
Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(
0xffffffff
);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(pixelsToSp(getContext(), textSize));
if
(highlightMode == HighlightMode.Background) {
highlightPaint =
new
Paint(Paint.ANTI_ALIAS_FLAG);
highlightPaint.setColor(highlightColor);
}
else
if
(highlightMode == HighlightMode.Image) {
highlightImage = createHighlightImage();
}
setClickable(
true
);
}
|
接下来的方法是createHighlightImage。当图像设置为高亮模式时,它会调用上面所示的方法。但是开头有一些比较棘手的代码。它需要得到图像的像素。然后处理像素 ——如果像素是不透明的(alpha != 0),就用高亮色值取代它,但是如果像素是透明的,那就不用改动。通过这种操作,我们创建了更高亮的图像。然后,我们将修改后的像素放回位图。并且在方法的最后,创建并返回BitmapDrawable。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
private
Drawable createHighlightImage() {
int
width = image.getIntrinsicWidth();
int
height = image.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas =
new
Canvas(bitmap);
image.setBounds(
0
,
0
, width, height);
image.draw(canvas);
int
count = bitmap.getWidth() * bitmap.getHeight();
int
pixels[] =
new
int
[count];
bitmap.getPixels(pixels,
0
, bitmap.getWidth(),
0
,
0
, bitmap.getWidth(), bitmap.getHeight());
for
(
int
n =
0
; n < count; n++) {
boolean
v = (Color.alpha(pixels[n])) !=
0
;
if
(v) {
int
pixel = pixels[n];
int
alpha = Color.alpha(pixel);
int
red = Color.red(highlightColor);
int
green = Color.green(highlightColor);
int
blue = Color.blue(highlightColor);
int
color = Color.argb(alpha, red, green, blue);
pixels[n] = color;
}
}
bitmap.setPixels(pixels,
0
, bitmap.getWidth(),
0
,
0
, bitmap.getWidth(), bitmap.getHeight());
return
new
BitmapDrawable(getResources(), bitmap);
}
|
为了处理状态变化,我们需要处理触摸事件。所以需要实现触摸处理。当我们触摸按钮时,它的状态就会变为pressed(按下),并重绘按钮中的内容。当按钮没有被触摸,那它的pressed标志就设置为false,并重绘按钮中的内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@Override
public
boolean
onTouchEvent(MotionEvent event) {
int
action = event.getActionMasked();
switch
(action) {
case
MotionEvent.ACTION_DOWN:
pressed =
true
;
invalidate();
break
;
case
MotionEvent.ACTION_UP:
pressed =
false
;
invalidate();
break
;
case
MotionEvent.ACTION_CANCEL:
case
MotionEvent.ACTION_OUTSIDE:
case
MotionEvent.ACTION_HOVER_EXIT:
pressed =
false
;
invalidate();
break
;
}
return
super
.onTouchEvent(event);
}
|
然后,我们实现onDraw按钮方法。此方法绘制了按钮的内容。自定义视图首次展示以及每次重绘时就调用这个onDraw方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
protected
void
onDraw(Canvas canvas) {
RectF bounds =
new
RectF(
0
,
0
, getWidth(), getHeight());
Drawable image =
null
;
Paint bgPaint =
null
;
switch
(highlightMode) {
case
None:
image =
this
.image;
bgPaint = pressed ? pressedBgpaint :
this
.bgpaint;
break
;
case
Background:
image =
this
.image;
if
(pressed)
bgPaint = pressedBgpaint;
else
bgPaint = highlighted ? highlightPaint :
this
.bgpaint;
break
;
case
Image:
image = highlighted ? highlightImage :
this
.image;
bgPaint = pressed ? pressedBgpaint :
this
.bgpaint;
break
;
}
if
(radius !=
0
.0f)
canvas.drawRoundRect(bounds, radius, radius, bgPaint);
else
canvas.drawRect(bounds, bgPaint);
Rect textBounds =
new
Rect();
if
(text.length() >
0
)
textPaint.getTextBounds(text,
0
, text.length(), textBounds);
float
h_dst = ((image !=
null
) ? image.getMinimumHeight() +
((text.length() >
0
) ? spacing :
0
) :
0
) + textBounds.height();
float
xd = (bounds.width() - ((image !=
null
) ? image.getMinimumWidth() :
0
)) /
2
;
float
yd = (bounds.height() - h_dst) /
2
;
if
(image !=
null
) {
image.setBounds((
int
) xd, (
int
) yd, (
int
)
(xd + image.getMinimumWidth()), (
int
) (yd + image.getMinimumHeight()));
image.draw(canvas);
}
float
xt = (bounds.width() -
0
* textBounds.width()) /
2
;
float
yt = yd + ((image !=
null
) ? image.getMinimumHeight() +
((text.length() >
0
) ? spacing :
0
) : textBounds.height());
// + textBounds.height();
canvas.drawText(text, xt, yt, textPaint);
if
(checked && checkable && checkedImage !=
null
) {
checkedImage.setBounds((
int
) (bounds.width() -
checkedImage.getMinimumWidth()), (
int
) (bounds.height() - checkedImage.getMinimumHeight()),
(
int
) bounds.width(), (
int
) bounds.height());
checkedImage.draw(canvas);
}
}
|
用法
为了整合RoundButton到代码,你需要下载源代码文件。在源代码文件中,有Eclipse项目,源代码和XML资源文件。你可以将它们复制到你的app项目中。或者编译RoundButton项目并将其作为库添加到你的项目。
如果你使用的是可视化编辑器,那就直接从控件列表中选择RoundButton,在添加它之后,设置其属性。
除了可视化编辑器,RoundButton既可以从布局XML,也可以从代码中插入。从布局XML添加的话,你可以这么使用。示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<RelativeLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:focusable=
"false"
android:focusableInTouchMode=
"false"
android:descendantFocusability=
"blocksDescendants"
android:orientation=
"horizontal"
xmlns:app=
"http://schemas.android.com/apk/res/com.bitgriff.bamp"
>
<com.bitgriff.bamp.helpers.RoundButton
android:id=
"@+id/button"
app:radius=
"0"
app:image=
"@drawable/ic_addtomedialibrary"
app:bgcolor=
"@color/transparent"
app:highlightMode=
"image"
android:layout_width=
"40dip"
android:layout_height=
"80dip"
android:layout_centerVertical=
"true"
android:layout_alignParentRight=
"true"
/>
</RelativeLayout>
|
从代码添加RoundButton,可以创造新的RoundButton实例。调用它的init方法传递图像(可为null),bgcolo和text。并添加RoundButton到你的ViewGroup:
1
2
|
roundButton =
new
RoundButton(context);
roundButton.init(image, bgcolor, text);
|
进一步设想
此外,我们还可以改变RoundButton的形状。例如,制作圆形按钮,正如现在很多Android app中所见的那样。也可能配置图像位置(left、right、top、bottom)。等等。
总结
这篇文章主要描述了如何实现根据状态改变背景的自定义按钮。这个简单的组件能为我们节省很多时间。希望能对你有用。
许可证
这篇文章,以及相关源代码和文件,都是经过The BSD License许可的。
译文链接: http://www.codeceo.com/article/android-button-color.html翻译作者: 码农网 – 小峰