標籤:
項目要求: 筆者曾經做過一個項目,其中登入介面的互動令人印象深刻。互動設計師給出了一個非常作的設計,要求做出包含根據情況可變色的底線,左側有可變表徵圖,右側有可變刪除標誌的輸入框,
記錄製作過程:
public class LineEditText extends EditText {
private Paint mPaint;private int color;public static final int STATUS_FOCUSED = 1;public static final int STATUS_UNFOCUSED = 2;public static final int STATUS_ERROR = 3;private int status = 2;private Drawable del_btn;private Drawable del_btn_down;private int focusedDrawableId = R.drawable.user_select;// 預設的private int unfocusedDrawableId = R.drawable.user;private int errorDrawableId = R.drawable.user_error;Drawable left = null;private Context mContext;
public LineEditText(Context context) {
super(context); mContext = context; init();}
public LineEditText(Context context, AttributeSet attrs) {
super(context, attrs); mContext = context; init();}
public LineEditText(Context context, AttributeSet attrs, int defStryle) {
super(context, attrs, defStryle); mContext = context; TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lineEdittext, defStryle, 0); focusedDrawableId = a.getResourceId( R.styleable.lineEdittext_drawableFocus, R.drawable.user_select); unfocusedDrawableId = a.getResourceId( R.styleable.lineEdittext_drawableUnFocus, R.drawable.user); errorDrawableId = a.getResourceId( R.styleable.lineEdittext_drawableError, R.drawable.user_error); a.recycle(); init();}
/** * 2014/7/31 * * @author Aimee.ZHANG */
private void init() { mPaint = new Paint(); // mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth(3.0f); color = Color.parseColor("#bfbfbf"); setStatus(status); del_btn = mContext.getResources().getDrawable(R.drawable.del_but_bg); del_btn_down = mContext.getResources().getDrawable(R.drawable.del_but_bg_down); addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } @Override public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } @Override public void afterTextChanged(Editable arg0) { setDrawable(); } }); setDrawable();}@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setColor(color); canvas.drawLine(0, this.getHeight() - 1, this.getWidth(), this.getHeight() - 1, mPaint);}// 刪除圖片private void setDrawable() { if (length() < 1) { setCompoundDrawablesWithIntrinsicBounds(left, null, del_btn, null); } else { setCompoundDrawablesWithIntrinsicBounds(left, null, del_btn_down,null); }}// 處理刪除事件@Overridepublic boolean onTouchEvent(MotionEvent event) { if (del_btn_down != null && event.getAction() == MotionEvent.ACTION_UP) { int eventX = (int) event.getRawX(); int eventY = (int) event.getRawY(); Log.e("eventXY", "eventX = " + eventX + "; eventY = " + eventY); Rect rect = new Rect(); getGlobalVisibleRect(rect); rect.left = rect.right - 50; if (rect.contains(eventX, eventY)) setText(""); } return super.onTouchEvent(event);}public void setStatus(int status) { this.status = status; if (status == STATUS_ERROR) { try { left = getResources().getDrawable(errorDrawableId); } catch (NotFoundException e) { e.printStackTrace(); } setColor(Color.parseColor("#f57272")); } else if (status == STATUS_FOCUSED) { try { left = getResources().getDrawable(focusedDrawableId); } catch (NotFoundException e) { e.printStackTrace(); } setColor(Color.parseColor("#5e99f3")); } else { try { left = getResources().getDrawable(unfocusedDrawableId); } catch (NotFoundException e) { e.printStackTrace(); } setColor(Color.parseColor("#bfbfbf")); } if (left != null) {
// left.setBounds(0, 0, 30, 40);
// this.setCompoundDrawables(left, null, null, null);
setCompoundDrawablesWithIntrinsicBounds(left,null,del_btn,null); } postInvalidate();}public void setLeftDrawable(int focusedDrawableId, int unfocusedDrawableId, int errorDrawableId) { this.focusedDrawableId = focusedDrawableId; this.unfocusedDrawableId = unfocusedDrawableId; this.errorDrawableId = errorDrawableId; setStatus(status);}@Overrideprotected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(focused, direction, previouslyFocusedRect); if (focused) { setStatus(STATUS_FOCUSED); } else { setStatus(STATUS_UNFOCUSED); }}@Overrideprotected void finalize() throws Throwable { super.finalize();};public void setColor(int color) { this.color = color; this.setTextColor(color); invalidate();}
}
:
代碼解釋:
變數名 STATUS_FOCUSED,STATUS_UNFOCUSED,STATUS_ERROR 標示了三種狀態,選中狀況為藍色,未選中狀態為灰色,錯誤狀態為紅色。focusedDrawableId unfocusedDrawableId errorDrawableId存放三種狀態的圖片,放置於最左側。
canvas.drawLine(0, this.getHeight() - 1, this.getWidth(),this.getHeight() - 1, mPaint); //畫editText最下方的線 setCompoundDrawablesWithIntrinsicBounds(left, null, del_btn, null); //放置左邊的和右邊的圖片(左,上,右,下) 相當於 android:drawableLeft="" android:drawableRight=""
- onTouchEvent 當手機點擊時,第一個先執行的函數,當點擊右側刪除表徵圖是清空 edittext
- setStatus 根據不同的狀態,左邊的圖片不一樣
存在的問題: 這版本雖然準系統已經實現,但是不符合需求,設計中要求文字框中無文字時,右側刪除按鈕不顯示,不點擊刪除按鈕,刪除按鈕要保持灰色,點擊時才可以變藍色。
因此有了第二個版本
public class LineEditText extends EditText implements TextWatcher,
OnFocusChangeListener{
private Paint mPaint;private int color;public static final int STATUS_FOCUSED = 1;public static final int STATUS_UNFOCUSED = 2;public static final int STATUS_ERROR = 3;private int status = 2;private Drawable del_btn;private Drawable del_btn_down;private int focusedDrawableId = R.drawable.user_select;// 預設的private int unfocusedDrawableId = R.drawable.user;private int errorDrawableId = R.drawable.user_error;Drawable left = null;private Context mContext;/** * 是否擷取焦點,預設沒有焦點 */ private boolean hasFocus = false; /** * 手指抬起時的X座標 */ private int xUp = 0; public LineEditText(Context context) { super(context); mContext = context; init();}public LineEditText(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; init();}public LineEditText(Context context, AttributeSet attrs, int defStryle) { super(context, attrs, defStryle); mContext = context; TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lineEdittext, defStryle, 0); focusedDrawableId = a.getResourceId( R.styleable.lineEdittext_drawableFocus, R.drawable.user_select); unfocusedDrawableId = a.getResourceId( R.styleable.lineEdittext_drawableUnFocus, R.drawable.user); errorDrawableId = a.getResourceId( R.styleable.lineEdittext_drawableError, R.drawable.user_error); a.recycle(); init();}/** * 2014/7/31 * * @author Aimee.ZHANG */private void init() { mPaint = new Paint(); // mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth(3.0f); color = Color.parseColor("#bfbfbf"); setStatus(status); del_btn = mContext.getResources().getDrawable(R.drawable.del_but_bg); del_btn_down = mContext.getResources().getDrawable(R.drawable.del_but_bg_down); addListeners(); setCompoundDrawablesWithIntrinsicBounds(left, null, null, null);}@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setColor(color); canvas.drawLine(0, this.getHeight() - 1, this.getWidth(), this.getHeight() - 1, mPaint);}// 刪除圖片
// private void setDrawable() { // if (length() < 1) { // setCompoundDrawablesWithIntrinsicBounds(left, null, null, null); // } else { // setCompoundDrawablesWithIntrinsicBounds(left, null, del_btn,null); // } // }
// 處理刪除事件@Overridepublic boolean onTouchEvent(MotionEvent event) { if (del_btn != null && event.getAction() == MotionEvent.ACTION_UP) { // 擷取點擊時手指抬起的X座標 xUp = (int) event.getX(); Log.e("xUp", xUp+""); /*Rect rect = new Rect(); getGlobalVisibleRect(rect); rect.left = rect.right - 50;*/ // 當點擊的座標到當前輸入框右側的距離小於等於 getCompoundPaddingRight() 的距離時,則認為是點擊了刪除表徵圖 if ((getWidth() - xUp) <= getCompoundPaddingRight()) { if (!TextUtils.isEmpty(getText().toString())) { setText(""); } } }else if(del_btn != null && event.getAction() == MotionEvent.ACTION_DOWN && getText().length()!=0){ setCompoundDrawablesWithIntrinsicBounds(left,null,del_btn_down,null); }else if(getText().length()!=0){ setCompoundDrawablesWithIntrinsicBounds(left,null,del_btn,null); } return super.onTouchEvent(event);}public void setStatus(int status) { this.status = status; if (status == STATUS_ERROR) { try { left = getResources().getDrawable(errorDrawableId); } catch (NotFoundException e) { e.printStackTrace(); } setColor(Color.parseColor("#f57272")); } else if (status == STATUS_FOCUSED) { try { left = getResources().getDrawable(focusedDrawableId); } catch (NotFoundException e) { e.printStackTrace(); } setColor(Color.parseColor("#5e99f3")); } else { try { left = getResources().getDrawable(unfocusedDrawableId); } catch (NotFoundException e) { e.printStackTrace(); } setColor(Color.parseColor("#bfbfbf")); } if (left != null) {
// left.setBounds(0, 0, 30, 40); // this.setCompoundDrawables(left, null, null, null); setCompoundDrawablesWithIntrinsicBounds(left,null,null,null); } postInvalidate(); }
public void setLeftDrawable(int focusedDrawableId, int unfocusedDrawableId, int errorDrawableId) { this.focusedDrawableId = focusedDrawableId; this.unfocusedDrawableId = unfocusedDrawableId; this.errorDrawableId = errorDrawableId; setStatus(status);} private void addListeners() { try { setOnFocusChangeListener(this); addTextChangedListener(this); } catch (Exception e) { e.printStackTrace(); } } @Overrideprotected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(focused, direction, previouslyFocusedRect); this.hasFocus=focused; if (focused) { setStatus(STATUS_FOCUSED); } else { setStatus(STATUS_UNFOCUSED); setCompoundDrawablesWithIntrinsicBounds(left,null,null,null); }}@Overrideprotected void finalize() throws Throwable { super.finalize();};public void setColor(int color) { this.color = color; this.setTextColor(color); invalidate();}@Overridepublic void afterTextChanged(Editable arg0) { // TODO Auto-generated method stub postInvalidate();}@Overridepublic void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { // TODO Auto-generated method stub if (TextUtils.isEmpty(arg0)) { // 如果為空白,則不顯示刪除表徵圖 setCompoundDrawablesWithIntrinsicBounds(left, null, null, null); } else { // 如果非空,則要顯示刪除表徵圖 setCompoundDrawablesWithIntrinsicBounds(left, null, del_btn, null); } }@Override public void onTextChanged(CharSequence s, int start, int before, int after) { if (hasFocus) { if (TextUtils.isEmpty(s)) { // 如果為空白,則不顯示刪除表徵圖 setCompoundDrawablesWithIntrinsicBounds(left, null, null, null); } else { // 如果非空,則要顯示刪除表徵圖 setCompoundDrawablesWithIntrinsicBounds(left, null, del_btn, null); } }
}
@Overridepublic void onFocusChange(View arg0, boolean arg1) { // TODO Auto-generated method stub try { this.hasFocus = arg1; } catch (Exception e) { e.printStackTrace(); } }
}
比較關鍵的方法是:onTouchEvent
當進入介面,點擊輸入框,要判斷輸入框中是否已有文字,如果有則顯示灰色的刪除按鈕,如果沒有則不顯示,如果點擊了刪除按鈕,刪除按鈕變藍色
存在的問題: 這個版本依舊存在問題,就是輸入長度超過輸入框,所畫的線不會延伸,
解決方案:
@Override
protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setColor(color); int x=this.getScrollX(); int w=this.getMeasuredWidth(); canvas.drawLine(0, this.getHeight() - 1, w+x, this.getHeight() - 1, mPaint);}
w:擷取控制項長度
X:延伸後的長度
最終效果:
在分享完這個介面的代碼設計後,筆者跟大家嘮一些新玩意。話說身處在帝都,如果不利用好帝都的豐厚資源,又如何對得起每天吸入的幾十斤霧霾?
話嘮的分享
在帝都生活,我每天早晨起來都會告訴自己,又是新的一天,要認真過。
寫一個 APP 很容易,寫好一個 APP 很難。如何檢驗自己所寫的 APP 的效能狀況,使用者體驗?
什麼是 APM?
In the fields of information technology and systems management, Application Performance Management (APM) is the monitoring and management of performance and availability of software applications. APM strives to detect and diagnose complex application performance problems to maintain an expected level of service. APM is "the translation of IT metrics into business meaning .
國內外有已很多成熟的 APM 廠商,筆者也曾染指過幾家,如AppDynamics,Newrelic,OneAPM
還有專註於 APP 崩潰監控的產品:Crashlytics,Crittercism,Bugly等
今天我想給大家分享的是從OneAPM Mobile Insight 產品中發現的一塊新大陸--卡頓監控
對流暢度的概念,相信大家並不陌生,即 1s 中之內繪圖重新整理訊號中斷的次數。流暢度次數越接近 40 時,使用者能感知到卡頓,流暢度在 20以下卡頓比較嚴重。OneAPM Mobile Insight的卡頓監控就是一個監控流暢度指標的模組。
- 卡頓趨勢圖:隨時間的推移,反饋卡頓發生次數的趨勢情況
- 裝置分布圖:卡頓現象集中分布的裝置類型
- 卡頓頁面:發生卡頓的頁面有哪些,其中平均流暢度是多少,卡頓了多少次等資訊。
查看單個頁面的卡頓情況,並從頁麵線程載入的情況中分析造成卡頓原因
如果你也想檢驗一下自己所寫的 APP 的使用者體驗情況,不妨試試這個新玩意~~
OneAPM Mobile Insight 以真實使用者體驗為度量標準進行 Crash 分析,監控網路請求及網路錯誤,提升使用者留存。訪問 OneAPM 官方網站感受更多應用效能最佳化體驗,想閱讀更多技術文章,請訪問 OneAPM 官方技術部落格。
Android 如何自訂EditText 底線?