自定義控件的步驟:
- 首先寫一個類,就是給控件起個名字
- 要在res/values目錄下建立attrs.xml文件,寫下需要定義的屬性
- 在自定義類,包含AttributeSet參數的構造方法中,關聯自定義屬性
- 將自定義的控件類放在布局文件中
- 在視圖類中使用
正文內容如下:
1、繼承View使用canvas繪制實例,自定義一個TextView
public class CustomTextView extends View{ private Paint mPaint;//畫筆 private int backColor;//背景色 private int textColor;//文字顏色 private float textSize;//文字大小 private String textContent;//文字內容 public CustomTextView(Context context) { super(context); } //關聯自定義屬性 public CustomTextView(Context context,AttributeSet attr) { super(context); TypedArray array = context.obtainStyledAttributes(attr, R.styleable.CustomTextView); textColor = array.getColor(R.styleable.CustomTextView_textColor, 0X000000); backColor = array.getColor(R.styleable.CustomTextView_backColor, 0XFFFFFF); textSize = array.getDimension(R.styleable.CustomTextView_textSize, 32); textContent = array.getString(R.styleable.CustomTextView_textContent); array.recycle(); } //開始繪畫 @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint = new Paint(); mPaint.setStyle(Style.FILL);//填充方式 mPaint.setTextSize(textSize); mPaint.setColor(backColor); canvas.drawRect(new Rect(10,10,200,100), mPaint); mPaint.setColor(textColor); canvas.drawText(textContent, 20, 60, mPaint); } }
attrs.xml文件內容如下:
<declare-styleable name="CustomTextView"> <attr name="backColor" format="color" /> <attr name="textColor" format="color" /> <attr name="textSize" format="dimension" /> <attr name="textContent" format="string" /> </declare-styleable>
2、繼承ImageView實例,自定義一個圓形圖片,適合做頭像用
類,屬性,構造方法如下:
public class RoundImageView extends ImageView
private int mBorderThickness = 0;//邊框厚度 private int mBorderOutsideColor = 0;//外邊框顏色 private int mBorderInsideColor = 0;//內邊框顏色 private int defaultColor = 0xFFFFFF;//默認使用顏色 private int defaultWidth = 0;//圖片寬度 private int defaultHeight = 0;//圖片高度 public RoundImageView(Context context) { super(context); } public RoundImageView(Context context, AttributeSet attrs) { super(context, attrs); setCustomAttributes(context, attrs); } public RoundImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setCustomAttributes(context, attrs); } private void setCustomAttributes(Context context, AttributeSet attrs) { TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.roundedimageview); mBorderThickness = array.getDimensionPixelSize( R.styleable.roundedimageview_border_thickness, 0); mBorderOutsideColor = array .getColor(R.styleable.roundedimageview_border_outside_color, defaultColor); mBorderInsideColor = array.getColor( R.styleable.roundedimageview_border_inside_color, defaultColor); array.recycle(); }
邊緣畫圓方法:
private void drawCircleBorder(Canvas canvas, int radius, int color) { Paint paint = new Paint(); /* 去鋸齒 */ paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); paint.setColor(color); /* 設置paint的 style 為STROKE:空心 */ paint.setStyle(Paint.Style.STROKE); /* 設置paint的外框寬度 */ paint.setStrokeWidth(mBorderThickness); canvas.drawCircle(defaultWidth / 2, defaultHeight / 2, radius, paint); }
主要的繪制方法,獲取裁剪圓形圖片方法
@Override protected void onDraw(Canvas canvas) { Drawable drawable = getDrawable(); if (drawable == null) { return; } if (getWidth() == 0 || getHeight() == 0) { return; } this.measure(0, 0); if (drawable.getClass() == NinePatchDrawable.class) return; Bitmap b = ((BitmapDrawable) drawable).getBitmap(); Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true); if (defaultWidth == 0) { defaultWidth = getWidth(); } if (defaultHeight == 0) { defaultHeight = getHeight(); } int radius = 0; if (mBorderInsideColor != defaultColor && mBorderOutsideColor != defaultColor) { // 定義畫兩個邊框,分別為外圓邊框和內圓邊框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2 - 2 * mBorderThickness; // 畫內圓 drawCircleBorder(canvas, radius + mBorderThickness / 2, mBorderInsideColor); // 畫外圓 drawCircleBorder(canvas, radius + mBorderThickness + mBorderThickness / 2, mBorderOutsideColor); } else if (mBorderInsideColor != defaultColor && mBorderOutsideColor == defaultColor) { // 定義畫一個邊框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2 - mBorderThickness; drawCircleBorder(canvas, radius + mBorderThickness / 2, mBorderInsideColor); } else if (mBorderInsideColor == defaultColor && mBorderOutsideColor != defaultColor) { // 定義畫一個邊框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2 - mBorderThickness; drawCircleBorder(canvas, radius + mBorderThickness / 2, mBorderOutsideColor); } else { // 沒有邊框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2; } Bitmap roundBitmap = getCroppedRoundBitmap(bitmap, radius); canvas.drawBitmap(roundBitmap, defaultWidth / 2 - radius, defaultHeight / 2 - radius, null); } /** * 獲取裁剪后的圓形圖片 */ public Bitmap getCroppedRoundBitmap(Bitmap bmp, int radius) { Bitmap scaledSrcBmp; int diameter = radius * 2; // 為了防止寬高不相等,造成圓形圖片變形,因此截取長方形中處於中間位置最大的正方形圖片 int bmpWidth = bmp.getWidth(); int bmpHeight = bmp.getHeight(); int squareWidth = 0, squareHeight = 0; int x = 0, y = 0; Bitmap squareBitmap; if (bmpHeight > bmpWidth) {// 高大於寬 squareWidth = squareHeight = bmpWidth; x = 0; y = (bmpHeight - bmpWidth) / 2; // 截取正方形圖片 squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else if (bmpHeight < bmpWidth) {// 寬大於高 squareWidth = squareHeight = bmpHeight; x = (bmpWidth - bmpHeight) / 2; y = 0; squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else { squareBitmap = bmp; } if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) { scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter, diameter, true); } else { scaledSrcBmp = squareBitmap; } Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight(), Config.ARGB_8888); Canvas canvas = new Canvas(output); Paint paint = new Paint(); Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight()); paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); canvas.drawARGB(0, 0, 0, 0); canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, paint); paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); canvas.drawBitmap(scaledSrcBmp, rect, rect, paint); bmp = null; squareBitmap = null; scaledSrcBmp = null; return output; }
attrs.xml文件內容如下:
<declare-styleable name="roundedimageview"> <attr name="border_thickness" format="dimension" /> <attr name="border_inside_color" format="color" /> <attr name="border_outside_color" format="color"></attr> </declare-styleable>
3、繼承ViewGroup,實現簡單滑動側邊欄菜單
public class SlideMenuView extends ViewGroup { private Scroller scroller;//滑動器 private final int MENU = 0;//顯示菜單標識 private final int MAIN = 1;//顯示主頁標識 private int startx;//起始X位置 private int currentScreen = MENU;//當前Screen public SlideMenuView(Context context, AttributeSet attrs) { super(context, attrs); scroller = new Scroller(context); } // 測量子view @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); View menu = getChildAt(0); menu.measure(menu.getLayoutParams().width, heightMeasureSpec); View main = getChildAt(1); main.measure(widthMeasureSpec, heightMeasureSpec); } // 將子view進行布局 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { View menu = getChildAt(0); menu.layout(-menu.getLayoutParams().width, t, 0, b); View main = getChildAt(1); main.layout(l, t, r, b); } // 觸摸事件 @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startx = (int) event.getX();// 記錄手指按下時,點到屏幕左邊的距離 break; case MotionEvent.ACTION_MOVE: int movex = (int) event.getX();// 移動后,手指點到屏幕左邊的距離 int diffx = startx - movex;// 屏幕左邊的偏移量 int newscrollx = getScrollX() + diffx;// 偏移后 if (newscrollx > 0) { scrollTo(0, 0);// 如果屏幕左邊超過了主界面左邊,那么讓屏幕左邊與主界面重合 } else if (newscrollx < -getChildAt(0).getWidth()) { scrollTo(-getChildAt(0).getWidth(), 0);// 如果屏幕左邊超過了側邊欄左邊,那么讓屏幕左邊與側邊欄左邊重合 } scrollBy(diffx, 0);// 持續偏移 startx = movex; break; case MotionEvent.ACTION_UP: int scrollx = getScrollX();// 屏幕左邊距離主界面左邊的距離,屏幕左邊在主界面左邊的左邊,為負值 if (scrollx > -getChildAt(0).getWidth() / 2) { currentScreen = MAIN;// 拖動屏幕不到側邊欄的一半時,放手,顯示主界面 switchScreen(); } else if (scrollx < -getChildAt(0).getWidth() / 2) { currentScreen = MENU;// 拖動屏幕超過了側邊欄的一般,放手,顯示側邊欄 switchScreen(); } break; default: break; } return true; } // 切換顯示側邊欄和主界面 private void switchScreen() { int dx = 0; // 獲得屏幕左邊距離主界面左邊的距離 int startX = getScrollX(); if (currentScreen == MAIN) { // 目標是將屏幕左邊與主界面左邊重合 dx = 0 - getScrollX(); } else if (currentScreen == MENU) { // 目標是將屏幕左邊與側邊欄的左邊重合 dx = -getChildAt(0).getWidth() - getScrollX(); } scroller.startScroll(startX, 0, dx, 0, Math.abs(dx) * 5); invalidate(); } // invalidate()的最終的調用方法就是computeScroll() 因此需要重寫該方法 @Override public void computeScroll() { if (scroller.computeScrollOffset()) { scrollTo(scroller.getCurrX(), 0); invalidate(); } } // 判斷當前顯示的是不是側邊欄 public boolean isMenuShow() { return currentScreen == MENU; } // 隱藏側邊欄 public void hideMenu() { currentScreen = MAIN; switchScreen(); } // 顯示側邊欄 public void showMenu() { currentScreen = MENU; switchScreen(); } }
4、自定義控件的使用
<com.android.myself.view.SlideMenuView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:myself="http://schemas.android.com/apk/res/com.android.myself" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="120dp" android:layout_height="match_parent" android:background="#ffd5d1" android:orientation="vertical" > </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.android.myself.view.RoundImageView android:layout_width="120dp" android:layout_height="120dp" android:scaleType="centerCrop" android:src="@drawable/jason" myself:border_inside_color="#fff7f2" myself:border_outside_color="#ffd5d1" myself:border_thickness="2dp" /> <com.android.myself.view.CustomTextView android:layout_width="wrap_content" android:layout_height="wrap_content" myself:backColor="#cccccc" myself:textColor="#FFFF00" myself:textContent="自定義" myself:textSize="16sp" /> </LinearLayout> </com.android.myself.view.SlideMenuView>
說明:xmlns:myself="http://schemas.android.com/apk/res/com.android.myself
com.android.myself是工程文件的包名
xmlns:myself是自定義的屬性標簽,可以隨意寫如xmlns:XXX,用的時候就是XXX:xxx
截圖一張如下:

源碼地址如下:http://files.cnblogs.com/files/pear-lemon/MySelf.zip
