Android EditText终极美化------带行号、下划线、弹性效果、光标美化

不多说,直接上代码:

需要说明的是,涉及到效率问题主要是在setText()和setPadding(),该方法用于调整行号的宽度,需要先知道最大行号数字的长度。所以需要先setText()把内容显示在EditText中,再通过getLineCount()获取最大行号,然后再setPadding(),这样的效率比较低,对于大量的数据可能会出现ANR。

解决方案就是把最大行号和内容一起保存起来,下次读取内容的时候先通过最大行号setPadding(),再setText()。


NoteEditText.java

public class NoteEditText extends EditText {

    private Context context;
    private boolean initialized;
    // 画笔 用来画下划线
    private Paint paint;
    private Paint mNumPaint;
    private Paint mRectPaint;
    private int mNumberLength;
    private float mTextSize; // sp

    private ScrollView mScrollView;

    public static final String KEY_TEXT_SIZE = "textSize";
    private String KEY_NUMBER_LENGTH = "_numberLength";
    public static final String KEY_SHOW_LINE = "showLine";
    public static final String KEY_SHOW_LINE_NUMBER = "showLineNumber";

    public static final float DEFAULT_VALUE_TEXT_SIZE = 20;
    public static final boolean DEFAULT_VALUE_SHOW_LINE = true;
    public static final boolean DEFAULT_VALUE_SHOW_LINE_NUMBER = false;
    private boolean showLine;
    private boolean showLineNumber;

    private int mPaddingLeft;

    private static final int LINE_OFFSET = 50;

    float displayPaddingLeft;

    public NoteEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
        setTextCursor();
    }

    // 自定义光标
    private void setTextCursor() {
        try {
            Method createEditorIfNeeded = ReflectionUtils.getMethod(
                    "android.widget.TextView", "createEditorIfNeeded", null);
            if (createEditorIfNeeded != null) {
                createEditorIfNeeded.setAccessible(true);
                createEditorIfNeeded.invoke(this, new Object[0]);
                Field editor = ReflectionUtils.getField(
                        "android.widget.TextView", "mEditor");
                if (editor != null) {
                    editor.setAccessible(true);
                    Field cursorDrawable = ReflectionUtils.getField(
                            "android.widget.Editor", "mCursorDrawable");
                    if (cursorDrawable != null) {
                        cursorDrawable.setAccessible(true);
                        Array.set(cursorDrawable.get(editor.get(this)), 0,
                                new LineSpaceCursorDrawable(context, this));
                        Array.set(cursorDrawable.get(editor.get(this)), 1,
                                new LineSpaceCursorDrawable(context, this));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void init() {
        showLine = Cache.read_Boolean(KEY_SHOW_LINE, DEFAULT_VALUE_SHOW_LINE);
        if (showLine) {
            paint = new Paint();
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(DisplayUtils.dip2px(context, 0.5f));
            paint.setColor(context.getResources().getColor(
                    R.color.note_edittext_line_color));
            // 开启抗锯齿 较耗内存
            paint.setAntiAlias(true);
        }
        showLineNumber = Cache.read_Boolean(KEY_SHOW_LINE_NUMBER,
                DEFAULT_VALUE_SHOW_LINE_NUMBER);
        if (showLineNumber) {
            mNumPaint = new Paint();
            mNumPaint.setStyle(Paint.Style.FILL);
            mNumPaint.setColor(context.getResources().getColor(
                    R.color.note_edittext_line_number_color));
            // 开启抗锯齿 较耗内存
            mNumPaint.setAntiAlias(true);

            mPaddingLeft = getPaddingLeft();

            mRectPaint = new Paint();
            mRectPaint.setStyle(Paint.Style.FILL);
            mRectPaint.setColor(context.getResources().getColor(
                    R.color.note_edittext_line_number_rect_color));// Color.rgb(153,
                                                                   // 148,
                                                                   // 252));
        }
        displayPaddingLeft = DisplayUtils.dip2px(context, 5);
        setTextSize(Cache.read_float(KEY_TEXT_SIZE, DEFAULT_VALUE_TEXT_SIZE));
    }

    // 添加弹性效果
    public void setScrollView(ScrollView scrollView) {
        mScrollView = scrollView;
    }

    @Override
    protected void onDraw(Canvas canvas) {

        try {
            if (!showLine && !showLineNumber)
                return;

            if (!initialized)
                return;

            // 得到总行数
            int lineCount = getLineCount();
            // 得到每行的高度
            int lineHeight = getLineHeight();

            int lineStart = 0;

            if (mScrollView != null) {
                lineStart = (mScrollView.getScrollY() - getPaddingTop())
                        / lineHeight - LINE_OFFSET;
                lineStart = lineStart >= 0 ? lineStart : 0;
            }
            int lineEnd = lineStart
                    + ScreenInfo.getInstance().getHeightPixels() / lineHeight
                    + LINE_OFFSET * 2;

            if (showLineNumber) {

                int numberLength = getNumberLength(lineCount);
                numberLength = numberLength > 2 ? numberLength : 2;
                if (numberLength != mNumberLength) {
                    Logger.d("numberLength != mNumberLength lineCount:"
                            + lineCount + " getVisibility:"
                            + this.getVisibility());
                    mNumberLength = numberLength;
                    Cache.write_int(KEY_NUMBER_LENGTH, mNumberLength);
                    adjustPadding();
                }

                // 绘制行号背景
                canvas.drawRect(new Rect(0, mScrollView.getScrollY()
                        - (int) DisplayUtils.dip2px(context, 600), mPaddingLeft
                        - (int) displayPaddingLeft, mScrollView.getScrollY()
                        + ScreenInfo.getInstance().getHeightPixels()
                        + (int) DisplayUtils.dip2px(context, 600)), mRectPaint);

                float textWidth = getNumberTextWidth(mNumPaint);

                for (int i = lineStart; i < lineEnd; i++) {
                    int x = (int) (mPaddingLeft - displayPaddingLeft
                            - DisplayUtils.dip2px(context, 15) / 2 - getNumberLength(i + 1)
                            * textWidth);
                    int y = ((i + 1) * getLineHeight()) - (getLineHeight() / 8);
                    canvas.drawText(String.valueOf(i + 1), x, y, mNumPaint);
                }
            }

            if (showLine) {
                // 根据行数循环画线
                for (int i = lineStart; i < lineEnd; i++) {
                    int lineY = (i + 1) * lineHeight;
                    canvas.drawLine(
                            0,
                            this.getPaddingTop()
                                    - ViewUtils.getLineSpacingExtra(context,
                                            this) / 2 + lineY,
                            this.getWidth(),
                            this.getPaddingTop()
                                    - ViewUtils.getLineSpacingExtra(context,
                                            this) / 2 + lineY, paint);
                }
            }
        } finally {
            super.onDraw(canvas);
        }

    }

    // 调整行号的宽度
    private void adjustPadding() {
        mPaddingLeft = (int) (DisplayUtils.dip2px(context, 15)
                + displayPaddingLeft + mNumberLength
                * getNumberTextWidth(mNumPaint));
        setPadding(mPaddingLeft, getPaddingTop(), getPaddingRight(),
                getPaddingBottom());
    }

    public void initText(CharSequence text) {
        if (!TextUtils.isEmpty(text))
            setText(text);
        initialized = true;
    }

    @Override
    public void setTextSize(float size) {
        Logger.d("mTextSize" + mTextSize);
        Logger.d("mTextSize size:" + size);
        Logger.d("mTextSize" + Calculator.equals(size, mTextSize));
        if (!Calculator.equals(size, mTextSize)) {
            super.setTextSize(size);
            mTextSize = size;
            Logger.d("mTextSize" + mTextSize);
            if (showLineNumber) {
                mNumPaint.setTextSize(getTextSize());
                adjustPadding();
            }
            Cache.write_float(KEY_TEXT_SIZE, mTextSize);
        }
    }

    public float getTextSizeSP() {
        return mTextSize;
    }

    public static float getNumberTextWidth(Paint paint) {
        float[] widths = new float[1];
        paint.getTextWidths("0", widths);
        return widths[0];
    }

    public void initNumberLength(String filePath) {
        if (showLineNumber) {
            KEY_NUMBER_LENGTH = filePath + KEY_NUMBER_LENGTH;
            Logger.d("KEY_NUMBER_LENGTH:" + KEY_NUMBER_LENGTH);
            mNumberLength = Cache.read_int(KEY_NUMBER_LENGTH,
                    getNumberLength(getLineCount()));
            adjustPadding();
        }
    }

    public static int getTextWidth(Paint paint, String str) {
        int iRet = 0;
        if (str != null && str.length() > 0) {
            int len = str.length();
            float[] widths = new float[len];
            paint.getTextWidths(str, widths);
            for (int j = 0; j < len; j++) {
                iRet += (int) Math.ceil(widths[j]);
            }
        }
        return iRet;
    }

    private int getNumberLength(int n) {
        int length = 0;
        while (n > 0) {
            n = n / 10;
            length++;
        }
        return length;
    }

}


自定义光标:
LineSpaceCursorDrawable.java

public class LineSpaceCursorDrawable extends ShapeDrawable {

    private Context context;
    private EditText view;

    public LineSpaceCursorDrawable(Context context, EditText view) {
        Logger.d("LineSpaceCursorDrawable new");
        this.context = context;
        setDither(false);
        Resources res = view.getResources();
        getPaint().setColor(res.getColor(ThemeManager.getInstance().background_color));//R.color.note_edittext_cursor_color));
        setIntrinsicWidth((int)DisplayUtils.dip2px(context, 2));//res.getDimensionPixelSize(R.dimen.detail_notes_text_cursor_width));
        this.view = view;
    }

    public void setBounds(int left, int top, int right, int bottom) {
        Logger.d("LineSpaceCursorDrawable setBounds");

        Editable s = view.getText();
        ImageSpan[] imageSpans = s.getSpans(0, s.length(),
                ImageSpan.class);
        int selectionStart = view.getSelectionStart();
        for (ImageSpan span : imageSpans) {
            int start = s.getSpanStart(span);
            int end = s.getSpanEnd(span);
            if (selectionStart >= start && selectionStart <= end)
            {
                super.setBounds(left, top, right, top - 1);
                return;
            }
        }
        super.setBounds(left, top, right, top + view.getLineHeight() - (int)ViewUtils.getLineSpacingExtra(context, view));

    }
}


ViewUtils.java
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public static float getLineSpacingExtra(Context context, TextView view){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            return view.getLineSpacingExtra();
        }
        else{
            return DisplayUtils.dip2px(context, 8);
        }
    }


DisplayUtils.java
    /** 
     * 将dip或dp值转换为px值,保证尺寸大小不变 
     *  
     * @param dipValue 
     * @param scale 
     *            (DisplayMetrics类中属性density) 
     * @return 
     */  
    public static float dip2px(Context context, float dipValue) {  
        float scale = context.getResources().getDisplayMetrics().density;  
        return dipValue * scale + 0.5f;  
    }


ScreenInfo.java
public class ScreenInfo {
    private int widthPixels;
    private int heightPixels;
    
    private static ScreenInfo instance;

    private ScreenInfo(Activity context) {
        DisplayMetrics dm = new DisplayMetrics();

        context.getWindowManager().getDefaultDisplay().getMetrics(dm);
        this.widthPixels = dm.widthPixels;
        this.heightPixels = dm.heightPixels;
    }
    
    public static void createInstance(Activity context){
        if(instance == null)
            instance = new ScreenInfo(context);
    }
    
    public static ScreenInfo getInstance(){
        return instance;
    }
    
    public static ScreenInfo getInstance(Activity context){
        if(instance == null)
            instance = new ScreenInfo(context);
        return instance;
    }

    /**
     * @return the number of pixel in the width of the screen.
     */
    public int getWidthPixels() {
        return widthPixels;
    }

    /**
     * @return the number of pixel in the height of the screen.
     */
    public int getHeightPixels() {
        return heightPixels;
    }
    
    public String getSize() {
        return widthPixels + "×" + heightPixels;
    }
}


ReflectionUtils.java
public class ReflectionUtils {
    private static final String TAG = "ReflectionUtils";

    public static boolean hasMethod(String className, String method, Class[] params) {
        try {
            Class<?> targetClass = Class.forName(className);
            if (params != null) {
                targetClass.getMethod(method, params);
                return true;
            }
            targetClass.getMethod(method, new Class[0]);
            return true;
        } catch (SecurityException e) {
            e.printStackTrace();
            return false;
        } catch (NoSuchMethodException e2) {
            e2.printStackTrace();
            return false;
        } catch (IllegalArgumentException e3) {
            e3.printStackTrace();
            return false;
        } catch (ClassNotFoundException e4) {
            e4.printStackTrace();
            return false;
        }
    }

    public static Method getMethod(String className, String method, Class[] params) {
        try {
            return Class.forName(className).getDeclaredMethod(method, params);
        } catch (SecurityException e) {
            e.printStackTrace();
            return null;
        } catch (NoSuchMethodException e2) {
            e2.printStackTrace();
            return null;
        } catch (IllegalArgumentException e3) {
            e3.printStackTrace();
            return null;
        } catch (ClassNotFoundException e4) {
            e4.printStackTrace();
            return null;
        }
    }

    public static Field getField(String className, String name) {
        try {
            return Class.forName(className).getDeclaredField(name);
        } catch (SecurityException e) {
            e.printStackTrace();
            return null;
        } catch (NoSuchFieldException e2) {
            e2.printStackTrace();
            return null;
        } catch (IllegalArgumentException e3) {
            e3.printStackTrace();
            return null;
        } catch (ClassNotFoundException e4) {
            e4.printStackTrace();
            return null;
        }
    }
}


NoteEditText的使用:


            <com.xxx.ElasticScrollView
            	android:id="@+id/scrollview"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:fastScrollEnabled="true"
                android:fillViewport="true" >
                <com.xxx.NoteEditText
                    android:id="@+id/content"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                    android:background="@null"
                    android:fadingEdge="vertical"
                    android:gravity="top"
                    android:paddingLeft="15dp"
                    android:paddingRight="15dp"
                    android:paddingTop="8dp"
                    android:paddingBottom="8dp"
                    android:scrollbars="vertical"
                    android:lineSpacingExtra="8dp"
                    android:text="" />
            </com.xxx.ElasticScrollView>


 

ElasticScrollView.java
/**
 * 有弹性的ScrollView
 * 实现下拉弹回和上拉弹回
 * @date Feb 13, 2014 6:11:33 PM
 */
public class ElasticScrollView extends ScrollView {
	
	private static final String TAG = "ElasticScrollView";
	
	//移动因子, 是一个百分比, 比如手指移动了100px, 那么View就只移动50px
	//目的是达到一个延迟的效果
	private static final float MOVE_FACTOR = 0.2f;
	
	//松开手指后, 界面回到正常位置需要的动画时间
	private static final int ANIM_TIME = 300;
	
	//ScrollView的子View, 也是ScrollView的唯一一个子View
	private View contentView; 
	
	//手指按下时的Y值, 用于在移动时计算移动距离
	//如果按下时不能上拉和下拉, 会在手指移动时更新为当前手指的Y值
	private float startY;
	
	//用于记录正常的布局位置
	private Rect originalRect = new Rect();
	
	//手指按下时记录是否可以继续下拉
	private boolean canPullDown = false;
	
	//手指按下时记录是否可以继续上拉
	private boolean canPullUp = false;
	
	//在手指滑动的过程中记录是否移动了布局
	private boolean isMoved = false;

	public ElasticScrollView(Context context) {
		super(context);
	}
	
	public ElasticScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@Override
	protected void onFinishInflate() {
		if (getChildCount() > 0) {
			contentView = getChildAt(0);
		}
	}
	
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		
		if(contentView == null) return;

		//ScrollView中的唯一子控件的位置信息, 这个位置信息在整个控件的生命周期中保持不变
		originalRect.set(contentView.getLeft(), contentView.getTop(), contentView
				.getRight(), contentView.getBottom());
	}
	
	/**
	 * 在该方法中获取ScrollView中的唯一子控件的位置信息
	 * 这个位置信息在整个控件的生命周期中保持不变
	 */
	
	/**
	 * 在触摸事件中, 处理上拉和下拉的逻辑
	 */
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		
		if (contentView == null) {
			return super.dispatchTouchEvent(ev);
		}

		int action = ev.getAction();
		
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			
			//判断是否可以上拉和下拉
			canPullDown = isCanPullDown();
			canPullUp = isCanPullUp();
			
			//记录按下时的Y值
			startY = ev.getY();
			break;
			
		case MotionEvent.ACTION_UP:
			
			if(!isMoved) break;  //如果没有移动布局, 则跳过执行
			
			// 开启动画
			TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(),
					originalRect.top);
			anim.setDuration(ANIM_TIME);
			
			contentView.startAnimation(anim);
			
			// 设置回到正常的布局位置
			contentView.layout(originalRect.left, originalRect.top, 
					originalRect.right, originalRect.bottom);
			
			//将标志位设回false
			canPullDown = false;
			canPullUp = false;
			isMoved = false;
			
			break;
		case MotionEvent.ACTION_MOVE:
			
			//在移动的过程中, 既没有滚动到可以上拉的程度, 也没有滚动到可以下拉的程度
			if(!canPullDown && !canPullUp) {
				startY = ev.getY();
				canPullDown = isCanPullDown();
				canPullUp = isCanPullUp();
				
				break;
			}
			
			//计算手指移动的距离
			float nowY = ev.getY();
			int deltaY = (int) (nowY - startY);
			
			//是否应该移动布局
			boolean shouldMove = 
					(canPullDown && deltaY > 0)    //可以下拉, 并且手指向下移动
					|| (canPullUp && deltaY< 0)    //可以上拉, 并且手指向上移动
					|| (canPullUp && canPullDown); //既可以上拉也可以下拉(这种情况出现在ScrollView包裹的控件比ScrollView还小)
			
			if(shouldMove){
				
				//计算偏移量
				int offset = (int)(deltaY * MOVE_FACTOR);
				
				//随着手指的移动而移动布局
				contentView.layout(originalRect.left, originalRect.top + offset,
						originalRect.right, originalRect.bottom + offset);
				
				isMoved = true;  //记录移动了布局
			}
			
			break;
		default:
			break;
		}

		return super.dispatchTouchEvent(ev);
	}
	

	/**
	 * 判断是否滚动到顶部
	 */
	private boolean isCanPullDown() {
		return getScrollY() == 0 || 
				contentView.getHeight() < getHeight() + getScrollY();
	}
	
	/**
	 * 判断是否滚动到底部
	 */
	private boolean isCanPullUp() {
		return  contentView.getHeight() <= getHeight() + getScrollY();
	}

}

((NoteEditText) editText).setScrollView((ScrollView) view
                .findViewById(R.id.scrollview));

效果展示,顺便打个广告:





评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值