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
ViewUtils.javapublic 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)); } }
DisplayUtils.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); } }
/**
* 將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.javapublic 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.javapublic 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));
效果展示,順便打個廣告:
相關推薦
Android EditText終極美化------帶行號、下劃線、彈性效果、游標美化
不多說,直接上程式碼: 需要說明的是,涉及到效率問題主要是在setText()和setPadding(),該方法用於調整行號的寬度,需要先知道最大行號數字的長度。所以需要先setText()把內容顯示在EditText中,再通過getLineCount()獲取最大行號,然後
分割槽函式Partition By、帶行號row_number()、排序rank()的用法詳解
partition by關鍵字是分析性函式的一部分,它和聚合函式不同的地方在於它能返回一個分組中的多條記錄,而聚合函式一般只有一條反映統計值的記錄,partition by用於給結果集分組,如果沒有指定那麼它把整個結果集作為一個分組,分割槽函式一般與排名函式一起使用。 準備測試資料:
一句話實現Mysql查詢結果帶行號
mys span sele font spa lec sql 行號 ont SELECT @rowno:=@rowno + 1 AS rowno,a.* FROM tableName a,(SELECT @rowno:=0) b一句話實現Mysql查詢結果帶行號
自帶行號的log日誌提示
HELLO ,I MISS YOU! 使用環境: 需要在列印log的同時,提示在專案類之中的哪一行。
Android EditText 自定義帶刪除按鈕
EditText裡面帶刪除按鈕在各種App裡面已經很常見了,如下: 所需的圖片資源: 下面直接貼上全部程式碼: public class ClearEditText extends EditText implements OnFocu
android採用MVP完整漫畫APP、釘釘地圖效果、功能完善的音樂播放器、仿QQ動態登入效果、觸手app主頁等原始碼
Android精選原始碼 Android優質部落格 應用不相容的常見原因使用了系統的 ClassLoader 載入 org.apache.http. 的庫Android M 就已經開始移除對 Apache HTTP client 的
UILable顯示不同的字型顏色、字型大小、行間距、首行縮排、下劃線等屬性(NSMutableAttributedString)
案例1:修改文字字型大小、顏色屬性 比如文字展示為姓名和性別,但是我們不能排除姓名會很長,所以此刻的lable寬度我們就不能寫死,換句話說lable的寬度根據文字的內容來定 我經常用兩種方式解決: 2.就是使用NSMutableAttributedString屬性
PHP正則匹配6到16位字符組合(且只能為數字、字母、下劃線)
lin asd fun 整合 bsp pass www. 正則 -m php正則匹配6到16位的字符串。 只允許包含數字、字母、下劃線組成的6到16位字符,符合返回ture,否則返回false。 解答: 6到16位,正則可以這樣寫:{6,16}。 任意的字符6到16位的正則
js物件屬性駝峰式命名(帶數字)轉下劃線命名
將類似於 info 格式轉化為 info2 var info = { id: 1, id1: 2, userName1: '劉玄德', userName2: '劉玄德', userName3: '大哥',
RichEditor——一款基於RecyclerView實現的富文字編輯器實現方案(支援圖文、轉義生成MarkDown、粗體、斜體、下劃線、刪除線、超連結、標題等)
前言 對於富文字編輯器的實現,首先我們肯定會想到實現的編輯器需要支援的幾個必要特性: 1.涉及大量文字,圖片,文字樣式的展示與編輯。 2.涉及極其複雜的使用者互動。 目前Github上我所瞭解的富文字編輯器基本上實現方式基於兩種: * 1.基於WebView
java驗證,”支援6-20個字母、數字、下劃線或減號,以字母開頭“這個的正則表示式怎麼寫?
轉自:https://yq.aliyun.com/wenzhang/show_96854 問題描述 java驗證,”支援6-20個字母、數字、下劃線或減號,以字母開頭“這個的正則表示式怎麼寫? 驗證”支援6-20個字母、數字、下劃線或減號,以字母開頭“的正則表示式
大家一起學python-day4-統計數字、字母、下劃線的數量
#第一種 #直接通過範圍查詢 shuzi =0 zhimu =0 xiahuaxian =0 qita =0 a = 'dsadasd34sda3dfsf_gfd???' b = len(a) for i in range(0,b): if a[i]>='1' and a[i]
專業乾貨!想強調重點該用粗體、下劃線還是斜體?
你以為的加粗很多是假粗體;下劃線只是打字機時代的產物,如今根本不能用;中文沒有斜體…. 以上僅僅是@justfont 這篇好文的眾多亮點之一,這不僅是一堂生動的字型科普課,也附上了強調文字的實用方法。只要你需要排版,這篇文章就不能放過,都是基礎功呀。 計算機輔助的設計中,一般人可以輕易的凸顯任何內文。只要透
iOS NSMutableAttributedString 實現富文字(不同顏色字型、下劃線等)
// 1> NSFontAttributeName(字型) // 該屬性所對應的值是一個 UIFont 物件。該屬性用於改變一段文字的字型。如果不指定該屬性,則預設為12-point Helvetica(Neue)。 // // 2> NSParagraphSt
Android開發技巧——去掉TextView中autolink的下劃線
我們知道,在佈局檔案中設定textview的autolink及其型別,這時textivew上會顯示link的顏色,並且文字下面會有一條下劃線,表示可以點選。而在我們在點選textview時,應用將根據我們所設定的型別跳轉到對應的介面。但是有時,我們因介面需求,需要去掉介面上
使用NSMutableAttributedString 實現富文字(不同顏色字型、下劃線等)
開發中,常常會有一段文字顯示不同的顏色和字型,或者給某幾個文字加刪除線或下劃線的需求。NSMuttableAttstring(帶屬性的字串),上面的一些需求都可以很簡便的實現。 1.例項化方法有兩種: 使用字串來初始化 ①:- (id)initWithSt
WPS文字設定奇偶頁首、下劃線的方法步驟
這個問題困擾了我一天,問了無數人都不會,最後還是自己無意間網上查到一個點給了我靈感。 本文將解決以下問題: 1、頁首新增下劃線、設定下劃線的粗細? 2、如何將奇偶頁的頁首設定成不一樣的格式,例如: 單
正則表示式——判斷字串組成,第一個必須是字母,後面可以是字母、數字、下劃線,總長度為5-20
//判斷字串是否是這樣組成的,第一個必須是字母,後面可以是字母、數字、下劃線,總長度為5-20 var c = /^[a-zA-Z]\w{4,19}$/; // /是轉義 ^ 是開頭 [
TextView測量文字長度、加粗、刪除線、下劃線
測量文字的長度 一定要記著post()與主執行緒直接獲取的區別,View的dispatchAttachedToWindow()方法呼叫才會執行runnable中的action,而這個方法設計Android底層UI邏輯處理。反正記住,post方法會在View的p
密碼6-20位,只允許字母、數字、下劃線其中兩項
最近專案的註冊模組優化,密碼的檢驗規則加強了,支援數字、字母、符號6-20位,必須包含其中至少兩種。開始用Google一頓狂search,然而並沒有找到合適的正則,本來都準備放棄正則,自己手寫判斷條件了。今天找一個類似的,然後自己試著改了下,居然成功了(此處容我