Android开发日记(十一)——Button控件+自定义Button控件

本系列原本写于博客园,现移植到自己的博客上并重新编辑。

一、基本Button控件

首先第一步就是往布局文件里拖一个Button控件,当然自己码出来也可以。XML布局如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"

>

<Button
android:id="@+id/button1" <!-- button按钮的id号,程序通过这个id号来查找相应的控件 -->
android:layout_width="wrap_content" <!-- button按钮的宽度 当前意思是 根据内容自动拉伸,其他的还有match_parent,表示根据父控件来调整大小-->
android:layout_height="wrap_content" <!-- button按钮的长度-->
android:layout_alignParentTop="true" <!-- RelativeLayout布局中,将控件的上边缘和父控件的上边缘对齐 -->
android:layout_centerHorizontal="true"<!-- RelativeLayout布局中,水平居中的意思 -->
android:layout_marginTop="150dp" <!-- RelativeLayout布局中,距离父控件顶端的距离 -->
android:text="Button" /> <!-- button按钮上显示的文字信息 -->

</RelativeLayout>

当然,一个控件的布局属性还有很多,这些都是需要我们多用多熟悉才行。
然后再在程序中调用它

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
public class MainActivity extends Activity {

private Button myButton;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//通过id寻找控件,记得寻找控件前一定要先设置好布局文件
myButton = (Button)findViewById(R.id.button1);
myButton.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
//这里填写单击按钮后要执行的事件
}

});
myButton.setOnTouchListener(new OnTouchListener(){...});//设置触碰到按钮的监听器
myButton.setOnLongClickListener(new OnLongClickListener(){...});//设置长按按钮的监听器
myButton.setOnHoverListener(new OnHoverListener(){...});//设置界面覆盖按钮时的监听器
//还有其它的的监听器,我们可以根据不同的需求来调用相应的监听器
}


}

或者这样设置监听器

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 class MainActivity extends Activity implements OnClickListener{

private Button myButton;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//寻找控件,记得寻找控件前一定要先设置好布局文件
myButton = (Button)findViewById(R.id.button1);
myButton.setOnClickListener(this);


}

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
//获取点击的View
switch(v.getId()){
//根据View的id来进行相关操作
case R.id.button1:
//按钮点击时处理相关的事件
break;
}
}


}

这样一个基础功能的button控件就完成了。但当然,这不是我们今天要讲的重点,重点是我们如何自定义一个按钮,而不是使用系统给我们的按钮。

二、自定义按钮

我们先来看看效果图吧
自定义按钮效果图
这是一个自带进度条的按钮,它可以显示异步任务的进度,当完成后结束操作。我们来看看具体是怎么实现的吧。
拆分这个按钮。仔细观察上面的效果图,我们可以把这个按钮分成3个部分,首先是最简单的外面一圈圆,基本上画出个圆放在那里就行了。接着是中间的三角形,正方形以及完成的勾,这个我们可以使用view里的画图类勾勒出来,再使用简单的动画Animation来切换。最后的一部分是覆盖在圆圈上的不断在表示进度的圆圈,这个我们可以不断调用这个view的ondraw来刷新进度。这就是整个按钮的设计思路。我们来看看实际的代码吧。
表示进度的圆圈,我们来新建一个CusImage继承view类,实时的传入进度参数。

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
    package com.example.mybutton;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;

@SuppressLint("ViewConstructor")
public class CusImage extends View {

private ButtonLayout b;
private Paint myPaint;
private float startAngle, sweepAngle;
private RectF rect;
// 默认控件大小
private int pix = 160;

public CusImage(Context context, ButtonLayout b) {
super(context);
this.b = b;
init();
// TODO Auto-generated constructor stub
}

public CusImage(Context context, AttributeSet attrs, ButtonLayout b) {
super(context, attrs);
this.b = b;
init();
// TODO Auto-generated constructor stub
}

private void init() {
myPaint = new Paint();
DisplayMetrics metrics = getContext().getResources()
.getDisplayMetrics();
int width = metrics.widthPixels;
int height = metrics.heightPixels;
Log.d("TAG", width + "");
Log.d("TAG", height + "");
float scarea = width * height;
pix = (int) Math.sqrt(scarea * 0.0217);

//抗锯齿
myPaint.setAntiAlias(true);
//stroke表示空心,Fill表示实心
myPaint.setStyle(Paint.Style.STROKE);
//颜色
myPaint.setColor(Color.rgb(0, 161, 234));
//设置线条粗细
myPaint.setStrokeWidth(7);

float startx = (float) (pix * 0.05);
float endx = (float) (pix * 0.95);
float starty = (float) (pix * 0.05);
float endy = (float) (pix * 0.95);
//矩形区域
rect = new RectF(startx, starty, endx, endy);
}

@Override
protected void onDraw(Canvas canvas) {
// 画弧线
// 在rect这个区域内画,开始的角度,扫过的度数而不是结束的角度,false表示不与圆心连线,true通常用来画扇形,画笔。
canvas.drawArc(rect, startAngle, sweepAngle, false, myPaint);
startAngle = -90;

//小于1圈
if (sweepAngle < 360 &&b.flg_frmwrk_mode == 2) {
invalidate();
}else if(b.flg_frmwrk_mode == 1){

}else {//扫完一圈,调用b.finalAnimation()
sweepAngle = 0;
startAngle = -90;
b.finalAnimation();

}
super.onDraw(canvas);
}

/**
* 控制控件的大小 http://blog.csdn.net/pi9nc/article/details/18764863
**/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = pix;
int desiredHeight = pix;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

int width;
int height;

// 如果控件宽度是指定大小,宽度为指定的尺寸
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) { // 没有限制,默认内容大小
width = Math.min(desiredWidth, widthSize);
} else {
width = desiredWidth;
}

// 如果控件高度是指定大小,高度为指定的尺寸
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {// 没有限制,默认内容大小
height = Math.min(desiredHeight, heightSize);
} else {
height = desiredHeight;
}
// 设定控件大小
setMeasuredDimension(width, height);
}
// 传入参数
public void setupprogress(int progress) {
sweepAngle = (float) (progress * 3.6);
}

public void reset() {
startAngle = -90;
}

}
```
有了表示进度的view之后,我们要在一个viewgroup控件中组装各个部分来实现整个按钮,这里我用的是framelayout
**ImageView的初始化**
```java
/**
* 创建各个控件
*/
private void initialise() {
// 按钮的进度条
cusView = new CusImage(getContext(), this);
// 按钮中间的形状
buttonimage = new ImageView(getContext());
// 完成进度后显示的图像
fillcircle = new ImageView(getContext());
//外面一圈圆
full_circle_image = new ImageView(getContext());
// 设置控件不接受点击事件
cusView.setClickable(false);
buttonimage.setClickable(false);
fillcircle.setClickable(false);
full_circle_image.setClickable(false);

setClickable(true);

}

设置动画

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
* 设置动画及动画监听器
*/
private void setAnimation() {

// Setting up and defining view animations.

// http://blog.csdn.net/congqingbin/article/details/7889778
// RELATIVE_TO_PARENT:与父控件的的中心为重点;RELATIVE_TO_SELF以自己为中心
// 左上角 分别为0.0f 0.0f 中心点为0.5f,0.5f 右下角1.0f,1.0f
/*
* arcRotation = new RotateAnimation(0.0f, 360.0f,
* Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
*/
// 持续时间1000ms
// arcRotation.setDuration(500);

in = new AnimationSet(true);
out = new AnimationSet(true);

// http://blog.csdn.net/jason0539/article/details/16370405
out.setInterpolator(new AccelerateDecelerateInterpolator());
in.setInterpolator(new AccelerateDecelerateInterpolator());

// http://blog.csdn.net/xsl1990/article/details/17096501
scale_in = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
scale_out = new ScaleAnimation(1.0f, 3.0f, 1.0f, 3.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);

// 缩放动画,起始x轴的缩放为0,y轴的缩放为0,动画后,x,y轴大小与图像尺寸相同
// x,y可以把它当做宽度和高度
new_scale_in = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);

new_scale_in.setDuration(200);

// 透明度的动画
fade_in = new AlphaAnimation(0.0f, 1.0f);
fade_out = new AlphaAnimation(1.0f, 0.0f);

scale_in.setDuration(150);
scale_out.setDuration(150);
fade_in.setDuration(150);
fade_out.setDuration(150);

// 进入的动画集
in.addAnimation(scale_in);
in.addAnimation(fade_in);
// 退出的动画集
out.addAnimation(fade_out);
out.addAnimation(scale_out);

out.setAnimationListener(new AnimationListener() {

@Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
System.out.println("print this");
}

@Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub

}

@Override
public void onAnimationEnd(Animation animation) {
// TODO Auto-generated method stub

buttonimage.setVisibility(View.GONE);
buttonimage.setImageBitmap(second_icon_bmp);
buttonimage.setVisibility(View.VISIBLE);
buttonimage.startAnimation(in);
full_circle_image.setVisibility(View.VISIBLE);
cusView.setVisibility(View.VISIBLE);

flg_frmwrk_mode = 2;

System.out.println("flg_frmwrk_mode" + flg_frmwrk_mode);

}
});

new_scale_in.setAnimationListener(new AnimationListener() {

@Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub

}

@Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub

}

@Override
public void onAnimationEnd(Animation animation) {
// TODO Auto-generated method stub
cusView.setVisibility(View.GONE);
buttonimage.setVisibility(View.VISIBLE);
buttonimage.setImageBitmap(third_icon_bmp);
flg_frmwrk_mode = 3;
buttonimage.startAnimation(in);

}
});

}

画出各个形状

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
* 设置各个画面的路径
*/
private void iconCreate() {

// Creating icons using path
// Create your own icons or feel free to use these

play = new Path();
play.moveTo(pix * 40 / 100, pix * 36 / 100);
play.lineTo(pix * 40 / 100, pix * 63 / 100);
play.lineTo(pix * 69 / 100, pix * 50 / 100);
play.close();

stop = new Path();
stop.moveTo(pix * 38 / 100, pix * 38 / 100);
stop.lineTo(pix * 62 / 100, pix * 38 / 100);
stop.lineTo(pix * 62 / 100, pix * 62 / 100);
stop.lineTo(pix * 38 / 100, pix * 62 / 100);
stop.close();

download_triangle = new Path();
download_triangle.moveTo(pix * 375 / 1000, (pix / 2)
+ (pix * 625 / 10000) - (pix * 3 / 100));
download_triangle.lineTo(pix / 2, (pix * 625 / 1000)
+ (pix * 625 / 10000) - (pix * 3 / 100));
download_triangle.lineTo(pix * 625 / 1000, (pix / 2)
+ (pix * 625 / 10000) - (pix * 3 / 100));
download_triangle.close();

download_rectangle = new Path();
download_rectangle.moveTo(pix * 4375 / 10000, (pix / 2)
+ (pix * 625 / 10000) - (pix * 3 / 100));
download_rectangle.lineTo(pix * 5625 / 10000, (pix / 2)
+ (pix * 625 / 10000) - (pix * 3 / 100));
download_rectangle.lineTo(pix * 5625 / 10000, (pix * 375 / 1000)
+ (pix * 625 / 10000) - (pix * 3 / 100));
download_rectangle.lineTo(pix * 4375 / 10000, (pix * 375 / 1000)
+ (pix * 625 / 10000) - (pix * 3 / 100));
download_rectangle.close();

tick = new Path();
tick.moveTo(pix * 30 / 100, pix * 50 / 100);
tick.lineTo(pix * 45 / 100, pix * 625 / 1000);
tick.lineTo(pix * 65 / 100, pix * 350 / 1000);

}

/**
* 创建各个bitmap添加到framelayout中
*/
public void init() {

// Defining and drawing bitmaps and assigning views to the layout

FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT);

lp.setMargins(10, 10, 10, 10);

fillcircle.setVisibility(View.GONE);

Bitmap.Config conf = Bitmap.Config.ARGB_8888; // see other conf types
Bitmap full_circle_bmp = Bitmap.createBitmap(pix, pix, conf);
Bitmap fill_circle_bmp = Bitmap.createBitmap(pix, pix, conf);

first_icon_bmp = Bitmap.createBitmap(pix, pix, conf); // Bitmap to draw
// first icon(
// Default -
// Play )

second_icon_bmp = Bitmap.createBitmap(pix, pix, conf); // Bitmap to draw
// second icon(
// Default -
// Stop )

third_icon_bmp = Bitmap.createBitmap(pix, pix, conf); // Bitmap to draw
// third icon(
// Default -
// Tick )

Canvas first_icon_canvas = new Canvas(first_icon_bmp);
Canvas second_icon_canvas = new Canvas(second_icon_bmp);
Canvas third_icon_canvas = new Canvas(third_icon_bmp);
Canvas fill_circle_canvas = new Canvas(fill_circle_bmp);
Canvas full_circle_canvas = new Canvas(full_circle_bmp);
float startx = (float) (pix * 0.05);
float endx = (float) (pix * 0.95);
System.out.println("full circle " + full_circle_canvas.getWidth()
+ full_circle_canvas.getHeight());
float starty = (float) (pix * 0.05);
float endy = (float) (pix * 0.95);
rect = new RectF(startx, starty, endx, endy);

first_icon_canvas.drawPath(play, fill_color); // Draw second icon on
// canvas( Default -
// Stop ).
// *****Set your second
// icon here****

second_icon_canvas.drawPath(stop, icon_color); // Draw second icon on
// canvas( Default -
// Stop ).
// *****Set your second
// icon here****

third_icon_canvas.drawPath(tick, final_icon_color); // Draw second icon
// on canvas(
// Default - Stop ).
// *****Set your
// second icon
// here****

full_circle_canvas.drawArc(rect, 0, 360, false, stroke_color);
fill_circle_canvas.drawArc(rect, 0, 360, false, fill_color);

buttonimage.setImageBitmap(first_icon_bmp);
flg_frmwrk_mode = 1;
fillcircle.setImageBitmap(fill_circle_bmp);
full_circle_image.setImageBitmap(full_circle_bmp);

cusView.setVisibility(View.GONE);

addView(full_circle_image, lp);
addView(fillcircle, lp);
addView(buttonimage, lp);
addView(cusView, lp);

}

最后加上点击按钮时各个状态切换的逻辑关系,这个按钮的布局就完成了。
按钮做好了我们可以在Activity中调用它了
将其写入到布局文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"

>

<com.example.mybutton.ButtonLayout
android:id="@+id/ButtonLayout01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:clickable="true" >
</com.example.mybutton.ButtonLayout>

</RelativeLayout>

在activity中设置

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    public class MainActivity extends Activity {

private static ButtonLayout buttonLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttonLayout = (ButtonLayout) findViewById(R.id.ButtonLayout01);
buttonLayout.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
buttonLayout.animation(); // Need to call this method for
// animation and progression

if (buttonLayout.flg_frmwrk_mode == 1) {

// Start state. Call any method that you want to execute

runOnUiThread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.this,
"Starting download", Toast.LENGTH_SHORT)
.show();
}
});
new DownLoadSigTask().execute();
}
if (buttonLayout.flg_frmwrk_mode == 2) {

// Running state. Call any method that you want to execute

new DownLoadSigTask().cancel(true);
buttonLayout.stop();
runOnUiThread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.this,
"Download stopped", Toast.LENGTH_SHORT)
.show();
}
});
}
if (buttonLayout.flg_frmwrk_mode == 3) {

// End state. Call any method that you want to execute.

runOnUiThread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.this,
"Download complete", Toast.LENGTH_SHORT)
.show();
}
});
}
}

});
}

static class DownLoadSigTask extends AsyncTask<String, Integer, String> {

@Override
protected void onPreExecute() {

}

@Override
protected String doInBackground(final String... args) {

// Creating dummy task and updating progress

for (int i = 0; i <= 100;) {
try {
Thread.sleep(50);

} catch (InterruptedException e) {

e.printStackTrace();
}
if (buttonLayout.flg_frmwrk_mode == 2 &&i<=100){
i++;
publishProgress(i);
}
}

return null;
}

@Override
protected void onProgressUpdate(Integer... progress) {

// publishing progress to progress arc

buttonLayout.cusView.setupprogress(progress[0]);
}

}

}

三、结束语

这个按钮我是仿照一个开源项目写的,它的地址是https://github.com/torryharris/TH-ProgressButton。
我在代码中掺杂了一些网址,这些都是我在看这整个开源代码时查阅的资料,如果你也不懂的话也可以去这些地址看看资料。
说实话,自定义控件设计的东西太多,不适合在入门的时候学习,不过有个概念,以此来抛砖引玉也是挺好的。

文章作者: cpacm
文章链接: http://www.cpacm.net/2015/04/01/Android开发日记(十一)——Button控件-自定义Button控件/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 cpacm
打赏
  • 微信
  • 支付宝

评论