Thứ Năm, 20 tháng 12, 2012


Creating a custom Android button with a resizable skin

Android Published on August 2, 2011 by Andrea Bresolin Add comments
Source code
If you are developing your own Android application, maybe you would like to give it a custom skin with a defined set of colors for all the interface elements. Here I’m going to show how to customize Android buttons. This is the same kind of customization I made for my own app which is called FWebLauncher and you can download for free from the Android Market. In this post I’m going to quickly describe the steps that have to be performed to obtain our result, but I recommend you to download the whole example application with all the resource images through the link on top of this post.
First of all, you need to decide how your buttons will look in different states, so you’ve got to define the corresponding skin images for the Normal, Pressed, Focused and Disabled states. Of course, if you don’t need all the states, you can just define some of them. In the following image you can see how our buttons will look like:

We want the skin to be resizable, so the button can adapt its size depending on the content (i.e. the text) without making the skin look weird. To do this, we need to define a 9-patch for each image. There’s a tool installed with the Android SDK that you can find in ${ANDROID_SDK_INSTALL_DIRECTORY}\tools\draw9patch.bat. Execute that batch file and you’ll have the following application running:

As you can see I’ve already loaded the PNG file for the Normal state of our buttons and I’ve defined the 9-patch with the black lines that you can see on every side of the image. As written in the Android developer’s documentation, the top and left lines define the stretchable area used to resize the image, while the bottom and right lines define the area where the content must be placed. With the 9-patch tool, I created a new file for each image of the button skin, so for example, if the skin image for the Normal state of the button is called button_normal.png, then the corresponding 9-patch file will be button_normal.9.png and we need only this inside the resources folders of our application.
The next thing to do is creating a selector that tells Android how it should deal with the skin images depending on the button state. The selector is just an XML file that looks like this:
1<?xml version="1.0" encoding="utf-8"?>
2<selector xmlns:android="http://schemas.android.com/apk/res/android">
3  <item android:drawable="@drawable/button_disabled" android:state_enabled="false"/>
4  <item android:drawable="@drawable/button_pressed" android:state_pressed="true"/>
5  <item android:drawable="@drawable/button_focused" android:state_focused="true"/>
6  <item android:drawable="@drawable/button_normal"/>
7</selector>
In the selector you define which skin image should be used to render each button state. So if the selector XML file is called button.xml, you can just write something like this to have your button skin working:
1<Button android:id="@+id/textBtn"
2  android:layout_width="wrap_content"
3  android:layout_height="wrap_content"
4  android:text="Text in the button"
5  android:background="@drawable/button"
6  android:textColor="#ffffffff"/>
This is just a simple button with some text in it. In case you need a button with an image, like an icon, and you want it to resize proportionally, then you don’t need to define a 9-patch and you need to use something different like an ImageButton:
1<ImageButton android:id="@+id/iconBtn"
2  android:layout_width="64dip"
3  android:layout_height="64dip"
4  android:src="@drawable/icon_button"
5  android:scaleType="fitCenter"
6  android:background="#00000000"/>
These are the possible states for iconBtn:

I set the scaleType attribute to make the image resize correctly and I set the background to #00000000 because I want to make it completely transparent (remember that the format for that attribute is ARGB where A is the alpha value). I don’t need the background because I already have an image for each state of the button with its own trasparency correctly set on the borders to have a custom shape for the button if I need it. The src attribute specifies the selector to use for this button:
1<?xml version="1.0" encoding="utf-8"?>
2<selector xmlns:android="http://schemas.android.com/apk/res/android">
3  <item android:drawable="@drawable/icon_button_disabled" android:state_enabled="false"/>
4  <item android:drawable="@drawable/icon_button_pressed" android:state_pressed="true"/>
5  <item android:drawable="@drawable/icon_button_focused" android:state_focused="true"/>
6  <item android:drawable="@drawable/icon_button_normal"/>
7</selector>
What if you want a button with both an icon and the text? Well, you can do it with something like this:
1<?xml version="1.0" encoding="utf-8"?>
2<Button android:id="@+id/textAndIconBtn"
3  android:layout_width="wrap_content"
4  android:layout_height="wrap_content"
5  android:text="Text in the button"
6  android:background="@drawable/button"
7  android:drawableLeft="@drawable/star_icon"
8  android:drawablePadding="5dip"
9  android:textColor="#ffffffff"/>
The icon is specified in the drawableLeft attribute and you can set also a padding to use between the icon and the text through the drawablePadding attribute. In this case the button.xml selector makes it possible to change only the background image depending on the button state, but not the icon itself. To do that, you need to act programmatically in the source code.
To see the final result of what I explained, take a look at the example application downloadable from the top of this post:

For each kind of button, you can try to make it pressed, focused or disabled to see how it changes. The layout of the main activity is defined as follows:
001<?xml version="1.0" encoding="utf-8"?>
002<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
003  android:orientation="vertical"
004  android:layout_width="fill_parent"
005  android:layout_height="fill_parent">
006 
007  <TextView
008    android:layout_width="wrap_content"
009    android:layout_height="wrap_content"
010    android:text="Button with text:"/>
011 
012  <LinearLayout
013    android:orientation="horizontal"
014    android:layout_width="wrap_content"
015    android:layout_height="wrap_content"
016    android:layout_marginTop="3dip"
017    android:layout_marginBottom="3dip">
018 
019    <Button android:id="@+id/textBtn"
020      android:layout_width="wrap_content"
021      android:layout_height="wrap_content"
022      android:text="Text in the button"
023      android:background="@drawable/button"
024      android:textColor="#ffffffff"
025      android:layout_gravity="center_vertical"/>
026 
027    <CheckBox android:id="@+id/textBtnEnabledCheck"
028      android:layout_width="wrap_content"
029      android:layout_height="wrap_content"
030      android:layout_marginLeft="10dip"
031      android:checked="true"
032      android:text="Enabled"
033      android:layout_gravity="center_vertical"/>
034 
035  </LinearLayout>
036 
037  <TextView
038    android:layout_width="wrap_content"
039    android:layout_height="wrap_content"
040    android:text="Button with icon:"
041    android:layout_marginTop="6dip"/>
042 
043  <LinearLayout
044    android:orientation="horizontal"
045    android:layout_width="wrap_content"
046    android:layout_height="wrap_content"
047    android:layout_marginTop="3dip"
048    android:layout_marginBottom="3dip">
049 
050    <ImageButton android:id="@+id/iconBtn"
051      android:layout_width="64dip"
052      android:layout_height="64dip"
053      android:src="@drawable/icon_button"
054      android:scaleType="fitCenter"
055      android:background="#00000000"
056      android:layout_gravity="center_vertical"/>
057 
058    <CheckBox android:id="@+id/iconBtnEnabledCheck"
059      android:layout_width="wrap_content"
060      android:layout_height="wrap_content"
061      android:layout_marginLeft="10dip"
062      android:checked="true"
063      android:text="Enabled"
064      android:layout_gravity="center_vertical"/>
065 
066  </LinearLayout>
067 
068  <TextView
069    android:layout_width="wrap_content"
070    android:layout_height="wrap_content"
071    android:text="Button with text and icon:"
072    android:layout_marginTop="6dip"/>
073 
074  <LinearLayout
075    android:orientation="horizontal"
076    android:layout_width="wrap_content"
077    android:layout_height="wrap_content"
078    android:layout_marginTop="3dip"
079    android:layout_marginBottom="3dip">
080 
081    <Button android:id="@+id/textAndIconBtn"
082      android:layout_width="wrap_content"
083      android:layout_height="wrap_content"
084      android:text="Text in the button"
085      android:background="@drawable/button"
086      android:drawableLeft="@drawable/star_icon"
087      android:drawablePadding="5dip"
088      android:textColor="#ffffffff"
089      android:layout_gravity="center_vertical"/>
090 
091    <CheckBox android:id="@+id/textAndIconBtnEnabledCheck"
092      android:layout_width="wrap_content"
093      android:layout_height="wrap_content"
094      android:layout_marginLeft="10dip"
095      android:checked="true"
096      android:text="Enabled"
097      android:layout_gravity="center_vertical"/>
098 
099  </LinearLayout>
100 
101</LinearLayout>
In the MainActivity class we just define the listeners to make the buttons enabled or disabled depending on the checkboxes. Note that you need to set the value also for the clickable property of the ImageButton to make it actually enabled or disabled.
01package com.devahead.customandroidbuttonwithresizableskin;
02 
03import android.app.Activity;
04import android.os.Bundle;
05import android.widget.Button;
06import android.widget.CheckBox;
07import android.widget.CompoundButton;
08import android.widget.CompoundButton.OnCheckedChangeListener;
09import android.widget.ImageButton;
10 
11public class MainActivity extends Activity implements OnCheckedChangeListener
12{
13  // Interface elements
14  protected Button textBtn;
15  protected CheckBox textBtnEnabledCheck;
16  protected ImageButton iconBtn;
17  protected CheckBox iconBtnEnabledCheck;
18  protected Button textAndIconBtn;
19  protected CheckBox textAndIconBtnEnabledCheck;
20 
21  @Override
22  public void onCreate(Bundle savedInstanceState)
23  {
24    super.onCreate(savedInstanceState);
25    setContentView(R.layout.main);
26 
27    // Retrieve interface elements
28    textBtn = (Button)findViewById(R.id.textBtn);
29    textBtnEnabledCheck = (CheckBox)findViewById(R.id.textBtnEnabledCheck);
30    iconBtn = (ImageButton)findViewById(R.id.iconBtn);
31    iconBtnEnabledCheck = (CheckBox)findViewById(R.id.iconBtnEnabledCheck);
32    textAndIconBtn = (Button)findViewById(R.id.textAndIconBtn);
33    textAndIconBtnEnabledCheck = (CheckBox)findViewById(R.id.textAndIconBtnEnabledCheck);
34 
35    // Add listeners
36    textBtnEnabledCheck.setOnCheckedChangeListener(this);
37    iconBtnEnabledCheck.setOnCheckedChangeListener(this);
38    textAndIconBtnEnabledCheck.setOnCheckedChangeListener(this);
39  }
40 
41  @Override
42  public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
43  {
44    if (buttonView == textBtnEnabledCheck)
45    {
46      textBtn.setEnabled(isChecked);
47    }
48    else if (buttonView == iconBtnEnabledCheck)
49    {
50      iconBtn.setEnabled(isChecked);
51      iconBtn.setClickable(isChecked);
52    }
53    else if (buttonView == textAndIconBtnEnabledCheck)
54    {
55      textAndIconBtn.setEnabled(isChecked);
56    }
57  }
58}
That’s it. I hope you can find something useful in this post and if you want to see some custom buttons in action in a real application, you could give a try to FWebLauncher.

From : http://www.devahead.com/blog/2011/08/creating-a-custom-android-button-with-a-resizable-skin/

Không có nhận xét nào:

Đăng nhận xét