項目要求: 筆者曾經作過一個項目,其中登陸界面的交互使人印象深入。交互設計師給出了一個很是做的設計,要求作出包含根據狀況可變色的下劃線,左側有可變圖標,右側有可變刪除標誌的輸入框,如圖 html
記錄製做過程:android
public class LineEditText extends EditText {
canvas
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) {
app
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 */ide
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(); } @Override protected 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); } } // 處理刪除事件 @Override public 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);post
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); } @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(focused, direction, previouslyFocusedRect); if (focused) { setStatus(STATUS_FOCUSED); } else { setStatus(STATUS_UNFOCUSED); } } @Override protected 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=""
存在的問題: 這版本雖然基本功能已經實現,可是不符合需求,設計中要求文本框中無文字時,右側刪除按鈕不顯示,不點擊刪除按鈕,刪除按鈕要保持灰色,點擊時才能夠變藍色。
所以有了第二個版本
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); } @Override protected 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); // } // }
// 處理刪除事件 @Override public 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(); } } @Override protected 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); } } @Override protected void finalize() throws Throwable { super.finalize(); }; public void setColor(int color) { this.color = color; this.setTextColor(color); invalidate(); } @Override public void afterTextChanged(Editable arg0) { // TODO Auto-generated method stub postInvalidate(); } @Override public 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); } }
}
@Override public 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 官方技術博客。
本文轉自 OneAPM 官方博客