Android自定义控件

自定义控件的步骤:

  1. 首先写一个类,就是给控件起个名字
  2. 要在res/values目录下建立attrs.xml文件,写下需要定义的属性
  3. 在自定义类,包含AttributeSet参数的构造方法中,关联自定义属性
  4. 将自定义的控件类放在布局文件中
  5. 在视图类中使用

正文内容如下:

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;
}
View Code

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

原文地址:https://www.cnblogs.com/pear-lemon/p/4611298.html