本文将介绍一种有效改变Android按钮颜色的方法。

按钮可以在状态改变时改变其颜色(例如按下,禁用,高亮显示)。但是,这需要一一说明每个状态。这篇文章将提供你一个根据状态变化轻松改变按钮颜色的方法。如果你正在写自定义视图,那么不妨也来读一读,因为中间我会涉及到如何用自定义属性实现自定义视图的相关内容。

如何实现

Android提供了灵活的绘制选择机制,可根据视图状态转变视图外观。每个状态通过一个单独的部分而存在。例如:在正常、禁用、按下、高亮状态下的按钮有着不同的背景颜色。请看下面的代码示例:

button_1_background.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!&mdash; 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_selectedbutton_1_focused,button_1_normal)必须定义在相应的在drawables目录下:

button_1_normal.xml

<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

<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

<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/button_1_selected_background"/>
<corners android:radius="10dip"/>
</shape>

然后设置按钮背景:

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

<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>

我们增加了 imagebgcolor,text,边框圆角半径,highlightColor和highlightMode属性。按下状态的颜色会从bgcolor导出(后面会描述的)。

实现按钮

首先,我们需要实现构造函数和解析参数。我们创建了3个不同的构造函数:

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,那就使用默认值。

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方法中创建图像的代 码,之后会一一给出。

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方法。它将颜色作为参数,并返回比该颜色更亮的颜色。这个方法也很简单:

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。

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,并重绘按钮中的内容。

@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方法。

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添加的话,你可以这么使用。示例如下:

<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:

roundButton = new RoundButton(context);
roundButton.init(image, bgcolor, text);

进一步设想

此外,我们还可以改变RoundButton的形状。例如,制作圆形按钮,正如现在很多Android app中所见的那样。也可能配置图像位置(left、right、top、bottom)。等等。

总结

这篇文章主要描述了如何实现根据状态改变背景的自定义按钮。这个简单的组件能为我们节省很多时间。希望能对你有用。

链接

RoundButton源代码

 

改变Android按钮背景颜色的高效方法的更多相关文章

  1. 如何改变Android标准键的颜色?

    本文选自StackOverflow(简称:SOF)精选问答汇总系列文章之一,本系列文章将为读者分享国外最优质的精彩问与答,供读者学习和了解国外最新技术,本文为大家讲解如何改变Android标准键的颜色 ...

  2. Android TextView背景颜色与背景图片设置

    Android TextView 背景颜色与背景图片设置,android textview 控件,android textview 背景, android textview 图片,android te ...

  3. 003android初级篇之【转】Android开发中颜色的定义方法

    正好用到颜色的定义,但脑子里没有记住具体,转载一篇加强印象 1.使用Color类的常量,如: int color = Color.BLUE; // 创建一个蓝色 是使用Android提供的颜色 int ...

  4. WPF中ListBox /ListView如何改变选中条背景颜色

    适用ListBox /ListView WPF中LISTVIEW如何改变选中条背景颜色 https://www.cnblogs.com/sjqq/p/7828119.html

  5. Java基础 awt Button 鼠标放在按钮上背景颜色改变,鼠标离开背景颜色恢复

        JDK :OpenJDK-11      OS :CentOS 7.6.1810      IDE :Eclipse 2019‑03 typesetting :Markdown   code ...

  6. Android使用属性动画ValueAnimator动态改变SurfaceView的背景颜色

    以下是主要代码,难点和疑问点都写在注释中: /** * 开始背景动画(此处为属性动画) */ private void startBackgroundAnimator(){ /* *参数解释: *ta ...

  7. Android之背景颜色小知识(笔记)

    一.ListView的item背景(自定义颜色) 通常情况下,ListView的item背景用的是图片资源,下面跟大家分享一下使用颜色资源,即自定义一种颜色,当item聚焦.按压.选择的时候,可以显示 ...

  8. 改变 TMemo 的背景颜色 (Firemonkey)

    说明:展示使用程序码改变 Firemonkey TMemo 的背景颜色. 适用:XE6 源码下载:[原創]Memo改背景色_XE6.zip //---------------------------- ...

  9. easyui 改变单元格背景颜色

    另外一种方法:https://www.cnblogs.com/raitorei/p/9989649.html easyui的datagrid改变整行颜色:https://www.cnblogs.com ...

随机推荐

  1. php5.4安装ecshopphp5.4问题及解决

    includes/cls_template.php line422 将 $tag_sel = array_shift(explode(" ", $tag)); 这句话拆开为两句. $tag_exp = ...

  2. Android 下拉刷新控件Android-PullToRefresh

    需要用到一个开源库 Android-PullToRefresh https://github.com/chrisbanes/Android-PullToRefresh ---------------- ...

  3. 【转】改善C#程序的建议2:C#中dynamic的正确用法 空间

    dynamic是FrameWork4.0的新特性.dynamic的出现让C#具有了弱语言类型的特性.编译器在编译的时候不再对类型进行检查,编译期默认dynamic对象支持你想要的任何特性.比如,即使你 ...

  4. npoi批量

    npoi批量导入实现及相关技巧 批量导入功能对于大部分后台系统来说都是不可或缺的一部分,常见的场景-基础数据的录入(部门,用户),用批量导入方便快捷.最近项目需要用到批量导入,决定花点时间写套比较通用 ...

  5. 【关于php】Appserv的安装注意事项

    之前的安装,问题应该不大,一路点“安装——Next——next·····”就行,下面是一些注意事项: (1)AppServ安装路径的选择.具体安装在哪个磁盘下用户可以自行选择,也可以使用默认的安装位置 ...

  6. 关于Java(常用数据类型)

    工作中,除非特殊需要,一般使用的数据类型较为单一. int int 是最常用的类型之一,一般能满足判断或循环的需求 float 或 double 两个浮点类型,可以在一定程度上确保数据的精度 BigD ...

  7. Java传递程序员变量

            解决方案虽然简单,不过若是想不起来就麻烦啦,好方法往往简单.         1.如何在一个java文件叫A里用另一个java文件叫B的方法叫method()?             ...

  8. Delphi 版本号(D1到XE6),发现一个delphi.wikia.com网站

    Borland Compiler Conditional Defines  Edit  Talk1 2,909PAGES ONTHIS WIKI   Product Name Version Cond ...

  9. SQL servere 范式、事务

    一.数据库范式: 1.构造数据库必须遵循一定的规则.在关系数据库中,这种规则就是范式. 范式是符合某一种级别的关系模式的集合.数据库中的关系必须满足一定的要求,即满足不同的范式. 满足最低要求的范式是 ...

  10. Android基础之响应Menu键弹出菜单Demo

    对于Android我也不是很熟悉,只是学习一些基本内容就OK.所以写的内容也很简单.本Demo要实现的效果就点击Menu键将弹出一个菜单并响应点击菜单项事件. 一.废话少说直接上代码.其实就是重写两个 ...