本文将介绍一种有效改变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. InstallShield 创建自己的Dialog

    1.在"User Interface"-"Dialogs"下,在All Dialogs右击"New Dialogs-"创建自己的Dialog ...

  2. 批处理协同blat自动发邮件

    Blat - A Windows (32 & 64 bit) command line SMTP mailer. Use it to automatically eMail logs, the ...

  3. windows“画图”工具用法

    图片编辑工具不少,photoshop功能强大,但是并不是那么容易掌握.而且,倘若一个简单的图片处理问题就使用这么强大的工具,有点“杀鸡焉用牛刀”的意思,要知道只打开photoshop就需要一段等待时间 ...

  4. WebApi(一)-实现跨域返回格式支持json

    1.创建webapi

  5. EFBaseDal新增删除方法

    public T Delete(int id )        {            var entity = db.Set<T>().Find(id);            T t ...

  6. C#使用字符串分割字符串

    我们都会用字符分割字符串: string[] recvArr = recv.Split(';'); 如果用字符串分割呢?下面: string[] sArray = Regex.Split(recv, ...

  7. Hadoop 学习笔记 (十) MapReduce实现排序 全局变量

    一些疑问:1 全排序的话,最后的应该sortJob.setNumReduceTasks(1);2 如果多个reduce task都去修改 一个静态的 IntWritable ,IntWritable会 ...

  8. document.styleSheets

    伪元素是不能选中的,如果非要改他的样式,两个方法. 静态方法: addClass的时候,新add的class带有新的伪元素. 动态方法: 如果知道它在document.styleSheets对象中的位 ...

  9. Storm学习笔记

    1.如何让一个spout并行读取多个流? 方法:任何spout.bolts组件都可以访问TopologyContext.利用这个特性可以让Spouts的实例之间划分流. 示例:获取到storm集群sp ...

  10. 14.8.1 Creating InnoDB Tables 创建InnoDB 表

    14.8.1 Creating InnoDB Tables 创建InnoDB 表 创建一个InnoDB表,使用CREATE TABLE 语句,你不需要指定ENGINE=InnoDB 子句 如果Inno ...