自定义控件的步骤:
- 首先写一个类,就是给控件起个名字
- 要在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
截图一张如下: