原文地址: http://www.cnblogs.com/baihai/archive/2012/08/20/2647533.html
方式一、继承原有的组件
实例:带图像的TextView
1、IconTextView.java
package net.blogjava.mobile.widget; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Rect; import android.util.AttributeSet; import android.widget.TextView; public class IconTextView extends TextView { // 命名空间的值 private final String namespace = "http://net.blogjava.mobile"; // 图像资源ID private int resourceId = 0; private Bitmap bitmap; //构造函数 public IconTextView(Context context, AttributeSet attrs) { //继承TextView的构造函数public TextView(Context context, AttributeSet attrs) super(context, attrs); resourceId = attrs.getAttributeResourceValue(namespace, "iconSrc", 0); if (resourceId > 0) bitmap = BitmapFactory.decodeResource(getResources(), resourceId); } @Override protected void onDraw(Canvas canvas) { if (bitmap != null) { // 从原图上截取图像的区域,在本例中为整个图像 Rect src = new Rect(); // 将截取的图像复制到bitmap上的目标区域,在本例中与复制区域相同 Rect target = new Rect(); src.left = 0; src.top = 0; src.right = bitmap.getWidth(); src.bottom = bitmap.getHeight(); int textHeight = (int) getTextSize(); target.left = 0; // 计算图像复制到目录区域的纵坐标。由于TextView中文本内容并不是从最顶端开始绘制的,因此,需要重新计算绘制图像的纵坐标 target.top = (int) ((getMeasuredHeight() - getTextSize()) / 2) + 1; target.bottom = target.top + textHeight; // 为了保证图像不变形,需要根据图像高度重新计算图像的宽度 target.right = (int) (textHeight * (bitmap.getWidth() / (float) bitmap.getHeight())); // 开始绘制图像 canvas.drawBitmap(bitmap, src, target, getPaint()); // 将TextView中的文本向右移动一定的距离(在本例中移动了图像宽度加2个象素点的位置) canvas.translate(target.right + 2, 0); } super.onDraw(canvas);// 必须最后调用 } }
2、main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:mobile="http://net.blogjava.mobile" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <net.blogjava.mobile.widget.IconTextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="第一个笑脸" mobile:iconSrc="@drawable/small" /> <net.blogjava.mobile.widget.IconTextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="第二个笑脸" android:textSize="24dp" mobile:iconSrc="@drawable/small" /> <net.blogjava.mobile.widget.IconTextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="第三个笑脸" android:textSize="36dp" mobile:iconSrc="@drawable/small" /> <net.blogjava.mobile.widget.IconTextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="第四个笑脸" android:textSize="48dp" mobile:iconSrc="@drawable/small" /> <net.blogjava.mobile.widget.IconTextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="第五个笑脸" android:textSize="36dp" mobile:iconSrc="@drawable/small" /> <net.blogjava.mobile.widget.IconTextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="第六个笑脸" android:textSize="24dp" mobile:iconSrc="@drawable/small" /> <net.blogjava.mobile.widget.IconTextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="第七个笑脸" mobile:iconSrc="@drawable/small" /> </LinearLayout>
3、效果
方式二、组合原有的组件
实例:带文本标签的EditText
1、LabelEditText.java
package net.blogjava.mobile.widget; import net.blogjava.mobile.R; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.widget.LinearLayout; import android.widget.TextView; public class LabelEditText extends LinearLayout { private TextView textView; private String labelText; private int labelFontSize; private String labelPosition; public LabelEditText(Context context, AttributeSet attrs) { super(context, attrs); // 读取labelText属性的资源ID int resourceId = attrs.getAttributeResourceValue(null, "labelText", 0); // 未获得资源ID,继续读取属性值 if (resourceId == 0) labelText = attrs.getAttributeValue(null, "labelText"); // 从资源文件中获得labelText属性的值 else labelText = getResources().getString(resourceId); // 如果按两种方式都未获得labelTex属性的值,表示未设置该属性,抛出异常 if (labelText == null) { throw new RuntimeException("必须设置labelText属性."); } // 获得labelFontSize属性的资源ID resourceId = attrs.getAttributeResourceValue(null, "labelFontSize", 0); // 继续读取labelFontSize属性的值,如果未设置该属性,将属性值设为14 if (resourceId == 0) labelFontSize = attrs.getAttributeIntValue(null, "labelFontSize", 14); // 从资源文件中获得labelFontSize属性的值 else labelFontSize = getResources().getInteger(resourceId); // 获得labelPosition属性的资源ID resourceId = attrs.getAttributeResourceValue(null, "labelPosition", 0); // 继续读取labelPosition属性的值 if (resourceId == 0) labelPosition = attrs.getAttributeValue(null, "labelPosition"); // 从资源文件中获得labelPosition属性的值 else labelPosition = getResources().getString(resourceId); // 如果未设置labelPosition属性值,将该属性值设为left if (labelPosition == null) labelPosition = "left"; String infService = Context.LAYOUT_INFLATER_SERVICE; LayoutInflater li; // 获得LAYOUT_INFLATER_SERVICE服务 li = (LayoutInflater) context.getSystemService(infService); LinearLayout linearLayout = null; // 根据labelPosition属性的值装载不同的布局文件 if ("left".equals(labelPosition)) linearLayout = (LinearLayout) li.inflate(R.layout.labeledittext_horizontal, this); else if ("top".equals(labelPosition)) linearLayout = (LinearLayout) li.inflate(R.layout.labeledittext_vertical, this); else throw new RuntimeException("labelPosition属性的值只能是left或top."); // 下面的代码从相应的布局文件中获得了TextView对象,并根据LabelTextView的属性值设置TextView的属性 textView = (TextView) findViewById(R.id.textview); textView.setTextSize((float) labelFontSize); textView.setText(labelText); } }
2、main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:id="@+id/textView" /> <net.blogjava.mobile.widget.LabelEditText android:id="@+id/first" android:layout_width="fill_parent" android:layout_height="wrap_content" labelFontSize="16" labelPosition="left" labelText="姓名:" /> <net.blogjava.mobile.widget.LabelEditText android:id="@+id/second" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" labelFontSize="26" labelPosition="top" labelText="兴趣爱好" /> </LinearLayout>
3、labeledittext_horizontal.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/textview" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <EditText android:id="@+id/edittext" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>
4、labeledittext_vertical.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/textview" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <EditText android:id="@+id/edittext" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>
5、效果
方式三、完全重写组件
实例:更换变盘指针后的时钟
1、HandClocl.java
package net.blogjava.mobile.widget; import java.util.Calendar; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.os.Handler; import android.util.AttributeSet; import android.view.View; public class HandClock extends View implements Runnable { private int clockImageResourceId; private Bitmap bitmap; private float scale; private float handCenterWidthScale; private float handCenterHeightScale; private int minuteHandSize; private int hourHandSize; private Handler handler = new Handler(); public void run() { // 重新绘制View invalidate(); // 重新设置定时器,在60秒后调用run方法 handler.postDelayed(this, 60 * 1000); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 根据图像的实际大小等比例设置View的大小 setMeasuredDimension((int) (bitmap.getWidth() * scale), (int) (bitmap.getHeight() * scale)); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setAntiAlias(true); Rect src = new Rect(); Rect target = new Rect(); src.left = 0; src.top = 0; src.right = bitmap.getWidth(); src.bottom = bitmap.getHeight(); target.left = 0; target.top = 0; target.bottom = (int) (src.bottom * scale); target.right = (int) (src.right * scale); // 画表盘图像 canvas.drawBitmap(bitmap, src, target, paint); // 计算表盘中心点的横纵坐标 float centerX = bitmap.getWidth() * scale * handCenterWidthScale; float centerY = bitmap.getHeight() * scale * handCenterHeightScale; // 表表盘中心点画一个半径为5的实心圆圈 canvas.drawCircle(centerX, centerY, 5, paint); // 设置分针为3个象素粗 paint.setStrokeWidth(3); Calendar calendar = Calendar.getInstance(); int currentMinute = calendar.get(Calendar.MINUTE); int currentHour = calendar.get(Calendar.HOUR); // 计算分针和时间的角度 double minuteRadian = Math.toRadians((360 - ((currentMinute * 6) - 90)) % 360); double hourRadian = Math.toRadians((360 - ((currentHour * 30) - 90)) % 360 - (30 * currentMinute / 60)); // 在表盘上画分针 canvas.drawLine(centerX, centerY, (int) (centerX + minuteHandSize * Math.cos(minuteRadian)) , (int) (centerY - minuteHandSize * Math.sin(minuteRadian)), paint); // 设置实针为4个象素粗 paint.setStrokeWidth(4); // 在表盘上画分针 canvas.drawLine(centerX, centerY, (int) (centerX + hourHandSize * Math.cos(hourRadian)) , (int) (centerY - hourHandSize * Math.sin(hourRadian)), paint); } public HandClock(Context context, AttributeSet attrs) { super(context, attrs); // 读取相应的属性值 clockImageResourceId = attrs.getAttributeResourceValue(null, "clockImageSrc", 0); if (clockImageResourceId > 0) bitmap = BitmapFactory.decodeResource(getResources(), clockImageResourceId); scale = attrs.getAttributeFloatValue(null, "scale", 1); handCenterWidthScale = attrs.getAttributeFloatValue(null, "handCenterWidthScale", bitmap.getWidth() / 2); handCenterHeightScale = attrs.getAttributeFloatValue(null, "handCenterHeightScale", bitmap.getHeight() / 2); // 在读取分针和时针长度后,将其值按图像的缩放比例进行缩放 minuteHandSize = (int) (attrs.getAttributeIntValue(null, "minuteHandSize", 0) * scale); hourHandSize = (int) (attrs.getAttributeIntValue(null, "hourHandSize", 0) * scale); int currentSecond = Calendar.getInstance().get(Calendar.SECOND); // 将定时器设在0分时执行run方法 handler.postDelayed(this, (60 - currentSecond) * 1000); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); // 删除回调类 handler.removeCallbacks(this); } }
完整代码:http://pan.baidu.com/share/link?shareid=22336&uk=1829692564