方式一、繼承原有的組件
實例:帶圖像的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