Android 自定义输入框

    现在的项目中,可能Android自带的输入框已经满足不了需求了,比如说密码框等等,今天就搞几个自定义的输入框,望大家指正。

参考来自YU  SplitEdittextView

Android 自定义输入框_第1张图片  Android 自定义输入框_第2张图片  Android 自定义输入框_第3张图片

如上效果图,有三种自定义输入框:

  • 分割的方形输入框
  • 下划线式的输入框
  • 连在一起的方形输入框

输入的内容也有两种形式:

  • 密码式的
  • 明文展示的

在自定义attr.xml文件中中定义了一些属性属性。

 
    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
            
            
            
            
        
        
        
        
        
            
            
            
            
            
            
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    

无非就是一些字体颜色、字体大小、输入框类型、输入框内容展示形式等,详情可以看代码注释。

下边开始上代码

  • 首先,做一些初始化的工作,获取attr属性,初始化画笔等工作
private void initAttrs(Context c, AttributeSet attrs) {
        TypedArray array = c.obtainStyledAttributes(attrs, R.styleable.SplitEditTextView);
        mBorderSize = array.getDimension(R.styleable.SplitEditTextView_borderSize, dp2px(1f));
        mBorderColor = array.getColor(R.styleable.SplitEditTextView_borderColor, Color.BLACK);
        mCornerSize = array.getDimension(R.styleable.SplitEditTextView_cornerSize, 0f);
        mDivisionLineSize = array.getDimension(R.styleable.SplitEditTextView_divisionLineSize, dp2px(1f));
        mDivisionColor = array.getColor(R.styleable.SplitEditTextView_divisionLineColor, Color.BLACK);
        mCircleRadius = array.getDimension(R.styleable.SplitEditTextView_circleRadius, dp2px(5f));
        mContentNumber = array.getInt(R.styleable.SplitEditTextView_contentNumber, 6);
        mContentShowMode = array.getInteger(R.styleable.SplitEditTextView_contentShowMode, CONTENT_SHOW_MODE_PASSWORD);
        mInputBoxStyle = array.getInteger(R.styleable.SplitEditTextView_inputBoxStyle, INPUT_BOX_STYLE_CONNECT);
        mSpaceSize = array.getDimension(R.styleable.SplitEditTextView_spaceSize, dp2px(10f));
        mTextSize = array.getDimension(R.styleable.SplitEditTextView_android_textSize, sp2px(16f));
        mTextColor = array.getColor(R.styleable.SplitEditTextView_android_textColor, Color.BLACK);
        mInputBoxSquare = array.getBoolean(R.styleable.SplitEditTextView_inputBoxSquare, true);
        mCursorColor = array.getColor(R.styleable.SplitEditTextView_cursorColor, Color.BLACK);
        mCursorDuration = array.getInt(R.styleable.SplitEditTextView_cursorDuration, 500);
        mCursorWidth = array.getDimension(R.styleable.SplitEditTextView_cursorWidth, dp2px(2f));
        mCursorHeight = (int) array.getDimension(R.styleable.SplitEditTextView_cursorHeight, 0);
        mUnderlineNormalColor = array.getInt(R.styleable.SplitEditTextView_underlineNormalColor, Color.BLACK);
        mUnderlineFocusColor = array.getInt(R.styleable.SplitEditTextView_underlineFocusColor, 0);
        array.recycle();
        init();
    }

    private void init() {
        mPaintBorder = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintBorder.setStyle(Paint.Style.STROKE);
        mPaintBorder.setStrokeWidth(mBorderSize);
        mPaintBorder.setColor(mBorderColor);

        mPaintDivisionLine = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintDivisionLine.setStyle(Paint.Style.STROKE);
        mPaintDivisionLine.setStrokeWidth(mDivisionLineSize);
        mPaintDivisionLine.setColor(mDivisionColor);

        mPaintContent = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintContent.setTextSize(mTextSize);

        mPaintCursor = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintCursor.setStrokeWidth(mCursorWidth);
        mPaintCursor.setColor(mCursorColor);

        mPaintUnderline = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintUnderline.setStrokeWidth(mBorderSize);
        mPaintUnderline.setColor(mUnderlineNormalColor);


        //单个输入框样式的RectF
        mRectFSingleBox = new RectF();
        //绘制Connect样式的矩形框
        mRectFConnect = new RectF();
        //设置单行输入
        setSingleLine();

        //EditText默认是获取焦点的
        setFocusableInTouchMode(true);

        //取消默认的光标
        setCursorVisible(false);
        //设置InputFilter,设置输入的最大字符长度为设置的长度
        setFilters(new InputFilter[]{new InputFilter.LengthFilter(mContentNumber)});
       
    }
  • 开始onDraw,根据不同类型的输入框,做相应的draw

    @Override
    protected void onDraw(Canvas canvas) {
        //绘制输入框
        switch (mInputBoxStyle) {
            case INPUT_BOX_STYLE_SINGLE:
                drawSingleStyle(canvas);
                break;
            case INPUT_BOX_STYLE_UNDERLINE:
                drawUnderlineStyle(canvas);
                break;
            case INPUT_BOX_STYLE_CONNECT:
            default:
                drawConnectStyle(canvas);
                break;
        }
        //绘制输入框内容
        drawContent(canvas);
        //绘制焦点光标
        drawCursor(canvas);
    }
  • 连在一起的方形输入框
/**
  * 绘制矩形外框
  */
    private void drawConnectStyle(Canvas canvas) {
        //每次重新绘制时,先将rectF重置下
        mRectFConnect.setEmpty();
        //需要减去边框的一半
        mRectFConnect.set(
                mBorderSize / 2,
                mBorderSize / 2,
                getWidth() - mBorderSize / 2,
                getHeight() - mBorderSize / 2
        );
        canvas.drawRoundRect(mRectFConnect, mCornerSize, mCornerSize, mPaintBorder);
        //绘制分割线
        drawDivisionLine(canvas);
    }
    /**
     * 分割线条数为内容框数目-1
     * 这里startX应该要加上左侧边框的宽度
     * 应该还需要加上分割线的一半
     * 至于startY和stopY不是 mBorderSize/2 而是 mBorderSize
     * startX是计算整个宽度的,需要算上左侧的边框宽度,所以不是+mBorderSize/2 而是+mBorderSize
     * startY和stopY:分割线是紧挨着边框内部的,所以应该是mBorderSize,而不是mBorderSize/2
     */
    private void drawDivisionLine(Canvas canvas) {
        float stopY = getHeight() - mBorderSize;
        for (int i = 0; i < mContentNumber - 1; i++) {
            //对于分割线条,startX = stopX
            float startX = (i + 1) * getContentItemWidth() + i * mDivisionLineSize + mBorderSize + mDivisionLineSize / 2;
            canvas.drawLine(startX, mBorderSize, startX, stopY, mPaintDivisionLine);
        }
    }

这里需要注意的是在绘制矩形以及其它图形的时候,矩形(图形)的边界是边框的中心,不是边框的边界,所以绘制矩形时的l,t,r,b四个参数都是去掉了边框宽度的一半(mBorderSize/2),不然会有误差。共勉!

 /**
     * 计算3种样式下,相应每个字符item的宽度
     */
    private float getContentItemWidth() {
        //计算每个密码字符所占的宽度,每种输入框样式下,每个字符item所占宽度也不一样
        float tempWidth;
        switch (mInputBoxStyle) {
            case INPUT_BOX_STYLE_SINGLE:
                //单个输入框样式:宽度-间距宽度(字符数-1)*每个间距宽度-每个输入框的左右边框宽度
                tempWidth = getWidth() - (mContentNumber - 1) * mSpaceSize - 2 * mContentNumber * mBorderSize;
                break;
            case INPUT_BOX_STYLE_UNDERLINE:
                //下划线样式:宽度-间距宽度(字符数-1)*每个间距宽度
                tempWidth = getWidth() - (mContentNumber - 1) * mSpaceSize;
                break;
            case INPUT_BOX_STYLE_CONNECT:
                //矩形输入框样式:宽度-左右两边框宽度-分割线宽度(字符数-1)*每个分割线宽度
            default:
                tempWidth = getWidth() - (mDivisionLineSize * (mContentNumber - 1)) - 2 * mBorderSize;
                break;
        }
        return tempWidth / mContentNumber;
    }
  • 绘制分割的方形输入框
    /**
     * 绘制分割的方形输入框

     */
    private void drawSingleStyle(Canvas canvas) {
        for (int i = 0; i < mContentNumber; i++) {
            mRectFSingleBox.setEmpty();
            float left = i * getContentItemWidth() + i * mSpaceSize + i * mBorderSize * 2 + mBorderSize / 2;
            float right = i * mSpaceSize + (i + 1) * getContentItemWidth() + (i + 1) * 2 * mBorderSize - mBorderSize / 2;
            //为避免在onDraw里面创建RectF对象,这里使用rectF.set()方法
            mRectFSingleBox.set(left, mBorderSize / 2, right, getHeight() - mBorderSize / 2);
            canvas.drawRoundRect(mRectFSingleBox, mCornerSize, mCornerSize, mPaintBorder);
        }
    }
  • 绘制下划线式德 输入框

    /**
     * 绘制下划线式de输入框
     * 线条起点startX:每个字符所占宽度itemWidth + 每个字符item之间的间距mSpaceSize
     * 线条终点stopX:stopX与startX之间就是一个itemWidth的宽度
     */
    private void drawUnderlineStyle(Canvas canvas) {
        String content = getText().toString().trim();
        for (int i = 0; i < mContentNumber; i++) {
            //计算绘制下划线的startX
            float startX = i * getContentItemWidth() + i * mSpaceSize;
            //stopX
            float stopX = getContentItemWidth() + startX;
            //对于下划线这种样式,startY = stopY
            float startY = getHeight() - mBorderSize / 2;
            //这里判断是否设置有输入框获取焦点时,下划线的颜色
            if (mUnderlineFocusColor != 0) {
                if (content.length() >= i) {
                    mPaintUnderline.setColor(mUnderlineFocusColor);
                } else {
                    mPaintUnderline.setColor(mUnderlineNormalColor);
                }
            }
            canvas.drawLine(startX, startY, stopX, startY, mPaintUnderline);
        }
    }
  • 绘制输入框内容
/**
     * 根据输入内容显示模式,绘制内容是密码类型的还是明文文本
     */
    private void drawContent(Canvas canvas) {
        int cy = getHeight() / 2;
        String password = getText().toString().trim();
        if (mContentShowMode == CONTENT_SHOW_MODE_PASSWORD) {
            mPaintContent.setColor(Color.BLACK);
            for (int i = 0; i < password.length(); i++) {
                float startX = getDrawContentStartX(i);
                canvas.drawCircle(startX, cy, mCircleRadius, mPaintContent);
            }
        } else {
            mPaintContent.setColor(mTextColor);
            //计算baseline
            float baselineText = getTextBaseline(mPaintContent, cy);
            for (int i = 0; i < password.length(); i++) {
                float startX = getDrawContentStartX(i);
                //计算文字宽度
                String text = String.valueOf(password.charAt(i));
                float textWidth = mPaintContent.measureText(text);
                //绘制文字x应该还需要减去文字宽度的一半
                canvas.drawText(text, startX - textWidth / 2, baselineText, mPaintContent);
            }
        }
    }
 /**
     * 计算绘制文本的基线
     *  固定方法
     * @param paint      绘制文字的画笔
     * @param halfHeight 高度的一半
     */
    private float getTextBaseline(Paint paint, float halfHeight) {
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        float dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
        return halfHeight + dy;
    }
  • 计算三种输入框样式下绘制圆和文字的x坐标

    /*
     * 计算三种输入框样式下绘制圆和文字的x坐标
     */
    private float getDrawContentStartX(int index) {
        switch (mInputBoxStyle) {
            case INPUT_BOX_STYLE_SINGLE:
                //单个输入框样式下的startX
                //即 itemWidth/2 + i*itemWidth + i*每一个间距宽度 + 前面所有的左右边框
                //   i = 0,左侧1个边框
                //   i = 1,左侧3个边框(一个完整的item的左右边框+ 一个左侧边框)
                //   i = ..., (2*i+1)*mBorderSize
                return getContentItemWidth() / 2 + index * getContentItemWidth() + index * mSpaceSize + (2 * index + 1) * mBorderSize;
            case INPUT_BOX_STYLE_UNDERLINE:
                //下划线输入框样式下的startX
                //即 itemWidth/2 + i*itemWidth + i*每一个间距宽度
                return getContentItemWidth() / 2 + index * mSpaceSize + index * getContentItemWidth();
            case INPUT_BOX_STYLE_CONNECT:
                //矩形输入框样式下的startX
                //即 itemWidth/2 + i*itemWidth + i*分割线宽度 + 左侧的一个边框宽度
            default:
                return getContentItemWidth() / 2 + index * getContentItemWidth() + index * mDivisionLineSize + mBorderSize;
        }
    }
  • 绘制焦点光标
 /**
     * 绘制光标
     * 光标只有一个,所以不需要根据循环来绘制,只需绘制第N个就行
     * 即:
     * 当输入内容长度为0,光标在第0个位置
     * 当输入内容长度为1,光标应在第1个位置
     * ...
     * 所以光标所在位置为输入内容的长度
     * 这里光标的长度默认就是 height/2
     */
    private void drawCursor(Canvas canvas) {
        if (mCursorHeight > getHeight()) {
            throw new InflateException("cursor height must smaller than view height");
        }
        String content = getText().toString().trim();
        float startX = getDrawContentStartX(content.length());
        //如果设置得有光标高度,那么startY = (高度-光标高度)/2+边框宽度
        if (mCursorHeight == 0) {
            mCursorHeight = getHeight() / 2;
        }
        int sy = (getHeight() - mCursorHeight) / 2;
        float startY = sy + mBorderSize;
        float stopY = getHeight() - sy - mBorderSize;

        //此时的绘制光标竖直线,startX = stopX
        canvas.drawLine(startX, startY, startX, stopY, mPaintCursor);
    }
  • 使光标闪烁,这里用的是通过设置光标的alpha值,来实现光标的闪烁
  /**
     * 光标Runnable
     * 通过Runnable每500ms执行重绘,每次runnable通过改变画笔的alpha值来使光标产生闪烁的效果
     */
    private class CursorRunnable implements Runnable {

        @Override
        public void run() {
            //获取光标画笔的alpha值
            int alpha = mPaintCursor.getAlpha();
            //设置光标画笔的alpha值
            mPaintCursor.setAlpha(alpha == 0 ? 255 : 0);
            invalidate();
            postDelayed(this, mCursorDuration);
        }
    }
 @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        cursorRunnable = new CursorRunnable();
        postDelayed(cursorRunnable, mCursorDuration);
    }

    @Override
    protected void onDetachedFromWindow() {
        removeCallbacks(cursorRunnable);
        super.onDetachedFromWindow();
    }
  • 输入文本之后的回调
/**
     * 通过复写onTextChanged来实现对输入的监听
     * 如果在onDraw里面监听text的输入长度来实现,会重复的调用该方法,就不妥当
     */
    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        String content = text.toString().trim();
        if (inputListener != null) {
            if (content.length() == mContentNumber) {
                inputListener.onInputFinished(content);
            } else {
                inputListener.onInputChanged(content);
            }
        }
    }


/**
 * 输入的监听抽象类
 */
public abstract class OnInputListener {

    /**
     * 输入完成的抽象方法
     * @param content 输入内容
     */
   public abstract void onInputFinished(String content);

    /**
     * 输入的内容
     * 定义一个空实现方法,让用户选择性的复写该方法,需要就复写,不需要就不用重写
     */
   public void onInputChanged(String text){

    }
}

测量view宽高,让每个输入框都是正方形的

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (mInputBoxSquare) {
            int width = MeasureSpec.getSize(widthMeasureSpec);
            //计算view高度,使view高度和每个item的宽度相等,确保每个item是一个正方形
            float itemWidth = getContentItemWidthOnMeasure(width);
            switch (mInputBoxStyle) {
                case INPUT_BOX_STYLE_UNDERLINE:
                    setMeasuredDimension(width, (int) (itemWidth + mBorderSize));
                    break;
                case INPUT_BOX_STYLE_SINGLE:
                case INPUT_BOX_STYLE_CONNECT:
                default:
                    setMeasuredDimension(width, (int) (itemWidth + mBorderSize * 2));
                    break;
            }
        }
    }
  • 下边是一些对外暴露的方法,设置输入框样式、设置输入框文本显示样式
/**
     * 设置密码是否可见
     */
    public void setContentShowMode(int mode) {
        if (mode != CONTENT_SHOW_MODE_PASSWORD && mode != CONTENT_SHOW_MODE_TEXT) {
            throw new IllegalArgumentException(
                    "the value of the parameter must be one of" +
                            "{1:EDIT_SHOW_MODE_PASSWORD} or " +
                            "{2:EDIT_SHOW_MODE_TEXT}"
            );
        }
        mContentShowMode = mode;
        invalidate();
    }

 /**
     * 设置输入框样式
     */
    public void setInputBoxStyle(int inputBoxStyle) {
        if (inputBoxStyle != INPUT_BOX_STYLE_CONNECT
                && inputBoxStyle != INPUT_BOX_STYLE_SINGLE
                && inputBoxStyle != INPUT_BOX_STYLE_UNDERLINE
        ) {
            throw new IllegalArgumentException(
                    "the value of the parameter must be one of" +
                            "{1:INPUT_BOX_STYLE_CONNECT}, " +
                            "{2:INPUT_BOX_STYLE_SINGLE} or " +
                            "{3:INPUT_BOX_STYLE_UNDERLINE}"
            );
        }
        mInputBoxStyle = inputBoxStyle;
     
        requestLayout();
      
    }
//设置监听
  public void setOnInputListener(OnInputListener listener) {
        this.inputListener = listener;
    }

使用方法:

 mSplitEditTextView = findViewById(R.id.splitTextView_single);
        mSplitEditTextView1 = findViewById(R.id.splitTextView_line);
        mSplitEditTextView2 = findViewById(R.id.splitTextView_whole);

        mSplitEditTextView.setInputBoxStyle(SplitEditTextView.INPUT_BOX_STYLE_SINGLE);
        mSplitEditTextView1.setInputBoxStyle(SplitEditTextView.INPUT_BOX_STYLE_UNDERLINE);
        mSplitEditTextView2.setInputBoxStyle(SplitEditTextView.INPUT_BOX_STYLE_CONNECT);

        mSplitEditTextView.setContentShowMode(SplitEditTextView.CONTENT_SHOW_MODE_TEXT);
        mSplitEditTextView1.setContentShowMode(SplitEditTextView.CONTENT_SHOW_MODE_TEXT);
        mSplitEditTextView2.setContentShowMode(SplitEditTextView.CONTENT_SHOW_MODE_TEXT);


        mSplitEditTextView.setOnInputListener(new OnInputListener() {
            @Override
            public void onInputFinished(String content) {

            }

            @Override
            public void onInputChanged(String text) {
                super.onInputChanged(text);
            }
        });

也可以通过xml实现一些

 
        />

可通过xml实现的一些属性值

属性名称	                属性说明	        默认值
borderSize	        边框宽度	        1dp
borderColor	        边框颜色	        Color.BLACK
conerSize	        边框圆角大小	0
divisionLineSize	分割线宽度	1dp
divisionLineColor	分割线颜色	Color.BLACK
circleRadius	        实心圆半径	5dp
contentNumber	        输入内容数量     6
contentShowMode	        内容显示模式     password
spaceSize	        输入框间距	10dp
inputBoxStyle	        输入框样式       connectBox
inputBoxSquare	        输入框是否正方形	true
cursorWidth	        光标宽度	        2dp
cursorHeight	        光标高度	        输入框高度的一半
cursorColor	        光标颜色	        Color.BLACK
cursorDuration	        闪烁时长	        500
underlineNormalColor    下划线normal颜色	Color.BLACK
underlineFocusColor	下划线focus颜色  未设置

源码在此,还望各位大佬批评指正哈

下载源码

你可能感兴趣的:(canvas,android)