目前三種顯示方式:前端
1.顯示具體數字數據庫
2.只顯示紅點json
3.顯示省略,表示數量不少canvas
1.顯示紅點:經過本地和服務器的時間戳對比,判斷是否要顯示紅點,每一個按鈕都有其對應的本地時間戳和服務器時間戳。api
2.隱藏紅點:當紅點顯示時,點擊把紅點隱藏。 並判斷是否要更新本地時間戳文件。緩存
3.紅點時間戳:時間戳須要進行緩存,方式爲寫入文件。服務器
1.點擊按鈕後是否要刷新該項的時間戳微信
2.因爲更新時間戳要寫入文件 因此儘快能少文件的保存ide
答:工具
1.只在按鈕爲紅點顯示狀態下點擊後更新本地時間戳文件。但在接口請求時不立刻寫入文件。緣由以下:
接口請求過程當中 -點擊了按鈕(沒法得知該按鈕記錄的時間是否在服務器裏時間的前面仍是後面 保存點擊時間 另起臨時Temp字段 不覆蓋當前本地Local時間字段)
(等請求完後調用回調,再對臨時字段的時間進行判斷比較)
接口請求成功 -點擊了按鈕(若是!showing的話,直接返回。若是showing,改變狀態,更新時間戳)
2.後臺接口參數加入index字段,每次後臺有更新服務器時間戳時將index升高,前端請求後對index進行判斷,不一樣則刷新本地時間戳並保存到文件,index相等說明數據庫時間戳沒有更新,就不處理。
方案從後臺的字段設計和前端的邏輯處理進行說明,代碼所列的順序爲:
後臺接口參數--》紅點控件(繼承ImageView)--》客服端接口類及方法(單例)--》監聽類和方法(方法寫在接口類中)--》Activity類中的方法
--》工具類Utils的方法(供Activity調用)--》時間戳Model(UserItemUpdateRecord)
接口:http://X.XX.XX.XXX/api/get_user_item_update_status?
請求方式GET
參數說明:
token=DQR3WF6Q56QW56QW //用戶惟一識別碼 可在後臺作分組 拓展參數
output { "code":0,//狀態碼,0表明成功 "msg":"",//發生錯誤的緣由 "data":{//具體數據
"index":0,//當前點擊記錄 "tip":[//各點時間戳 { "type":"home", "date":"2015-09-01",
"count":0,//內容更新數量
}, { "type":"infomation", "date":"2015-11-11",
"count":5,
}, { "type":"me", "date":"2016-01-15",
"count":15,
}
]
}
這裏紅點控件根據數量分紅三類,數量判斷標準本身定。
public class RedPointImageView extends ImageView { //紅點長度類型 private static final int TYPE_ZERO = 0; private static final int TYPE_SHORT = 1; private static final int TYPE_LONG = 2; private int type; //保存onSizeChange()裏的寬高 private int width; private int height; //按鈕Tag,用來識別 private String mTag; private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Rect mRect = new Rect(); private RelativeLayout mRlPoint = null; private Drawable mDPoint = null; private int radius; private boolean mShow = false; private int number; private TextView mTvPoint; public RedPointImageView(Context context) { super(context); this.number = -1; init(); } public RedPointImageView(Context context, AttributeSet attrs) { super(context, attrs); this.number = -1; init(); } public RedPointImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.number = -1; init(); } private void init() { if(number<0)return; //數量小於0直接返回 mPaint.setFilterBitmap(true); radius = getResources().getDimensionPixelSize(R.dimen.red_point_radius); mRlPoint = new RelativeLayout(getContext()); mTvPoint = new TextView(getContext()); mTvPoint.setTextSize(14); mTvPoint.setTextColor(getResources().getColor(R.color.white)); RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); params1.setMargins(0,0,0,0); params1.addRule(RelativeLayout.CENTER_IN_PARENT); mRlPoint.addView(mTvPoint,params1); initUI(); } private void initUI(){ if(number == 0){ //ZERO類型 mTvPoint.setText(""); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(getResources().getDimensionPixelOffset(R.dimen.margin_8),getResources().getDimensionPixelOffset(R.dimen.margin_8)); params.setMargins(0,0,0,0); mRlPoint.setLayoutParams(params); mRlPoint.setBackgroundResource(R.drawable.icon_red_point); type = TYPE_ZERO; }else if(number>0&&number<10){ //SHORT類型 mTvPoint.setText(String.valueOf(number)); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(getResources().getDimensionPixelOffset(R.dimen.margin_15),getResources().getDimensionPixelOffset(R.dimen.margin_15)); params.setMargins(0,0,0,0); mRlPoint.setLayoutParams(params); mRlPoint.setBackgroundResource(R.drawable.icon_red_point); type = TYPE_SHORT; }else{ //LONG類型 mTvPoint.setText("···"); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(getResources().getDimensionPixelOffset(R.dimen.margin_20),getResources().getDimensionPixelOffset(R.dimen.margin_12)); params.setMargins(0,0,0,0); mRlPoint.setLayoutParams(params); mRlPoint.setBackgroundResource(R.drawable.bg_corner_red); type = TYPE_LONG; } mDPoint = new BitmapDrawable(null,convertViewToBitmap(mRlPoint)); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width = w; height = h; updateRect(w, h); } private void updateRect(int w, int h) { int left,top,right,bottom; if(type == TYPE_SHORT){ left = w - radius; top = 0; right = w; bottom = radius; }else if(type == TYPE_ZERO){ left = w - radius*2/3; top = 0; right = w; bottom = radius*2/3; }else{ left = w - radius/3*4; top = 0; right = w; bottom = radius/5*4; } mRect.set(left, top, right, bottom); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mShow) { drawRedPoint(canvas); } } private void drawRedPoint(Canvas canvas) { if (mDPoint == null) { return; } canvas.save(); // canvas.clipRect(mRect, Region.Op.DIFFERENCE); mDPoint.setBounds(mRect); mDPoint.draw(canvas); canvas.restore(); } public void setShow(boolean isShow){ this.mShow = isShow; invalidate(); } public boolean isShow(){ return mShow; } public String getmTag() { return mTag; } public void setmTag(String mTag) { this.mTag = mTag; } public void updateItem(){ UserItemUpdateRecord userItemUpdateRecord = IpinClient.getInstance().getAccountManager().getUserItemUpdateRecord(); if(userItemUpdateRecord!=null){ userItemUpdateRecord.refreshUpdateImg(this); } } public void setNumber(int number){ this.number = number; if(number<0) mShow = false; else mShow = true; init(); onSizeChanged(width,height,width,height); invalidate(); } public static Bitmap convertViewToBitmap(View view){ view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); view.buildDrawingCache(); Bitmap bitmap = view.getDrawingCache(); return bitmap; } }
1.tryToLoadUserItemStatus() --> 從本地文件裏讀取紅點時間戳對象
2.requestUserItemStatus() --> 接口請求服務器端的紅點時間戳
3.saveItemUpdateStatusToFile() --> 保存紅點時間戳到本地文件
public static final String URL_GET_USER_ITEM_STATUS = "http://m.gaokao.ipin.com/api/get_user_item_update_status?";//獲取用戶按鈕更新信息 public static final String FILE_NAME_USER_ITEM_STATUS = "user_item_status.ipin";//按鈕更新狀態文件 private AtomicBoolean isLoadedUserItemStatus = new AtomicBoolean(false); private HashSet<OnUpdateItemStatusListener> mOnUpdateItemStatusListeners = new HashSet<>();//監聽列表 private UserItemUpdateRecord userItemUpdateRecord;//時間戳model private boolean isRequestingUserItemStatus = false;//是否正在獲取數據 /** * 從文件中讀取按鈕更新信息 */ private void tryToLoadUserItemStatus(){ TaskExecutor.getInstance().post(new Runnable() { @Override public void run() { synchronized (AccountManager.class) { boolean b = checkAndCopyUserData(AccountConstant.FILE_NAME_USER_ITEM_STATUS); if (!b) { isLoadedUserItemStatus.set(true); requestUserItemStatus(); return; } String path = StorageManager.getInstance().getPackageFiles() + AccountConstant.FILE_NAME_USER_ITEM_STATUS; Object object = FileUtil.readObjectFromPath(path); if (object != null && object instanceof UserItemUpdateRecord) { userItemUpdateRecord = (UserItemUpdateRecord) object; } isLoadedUserItemStatus.set(true); requestUserItemStatus(); } } }); }
private boolean checkAndCopyUserData(String path) {
String newPath = StorageManager.getInstance().getPackageFiles() + path;
String oldPath = StorageManager.getInstance().getPackageCacheRoot() + path;
File newFile = new File(newPath);
if (newFile.exists()) {
return true;
}
File oldFile = new File(oldPath);
if (!oldFile.exists()) {
return false;
}
return oldFile.renameTo(newFile);
}
/** * 請求服務器更新按鈕的時間戳 */ public void requestUserItemStatus(){ isRequestingUserItemStatus = true; final IRequest request = (IRequest) IpinClient.getInstance().getService(IpinClient.SERVICE_HTTP_REQUEST); if (request == null) { return; } request.sendRequestForPostWithJson(AccountConstant.URL_GET_USER_ITEM_STATUS, getParamForGetUserInfo(getIpinToken()), new IRequestCallback() { @Override public void onResponseSuccess(JSONObject jsonObject) { if (jsonObject == null) { return; } if(jsonObject.getInteger(Constant.KEY_CODE)!=0)return; if(jsonObject.getJSONObject(Constant.KEY_DATA)==null)return; jsonObject = jsonObject.getJSONObject(Constant.KEY_DATA); if(userItemUpdateRecord!=null&&userItemUpdateRecord.getIndex() == jsonObject.getInteger("index"))
return;//若是服務器的index與本地的同樣,則目前已經保存最新的更新時間戳,不執行更新本地時間戳操做 UserItemUpdateRecord updateRecord = new UserItemUpdateRecord(); updateRecord.decode(jsonObject); userItemUpdateRecord = updateRecord; isRequestingUserItemStatus = false; dispatchOnUpdateItemStatusListener(); TaskExecutor.getInstance().post(new Runnable() { @Override public void run() { saveItemUpdateStatusToFile();//將時間戳保存至文件 } }); } @Override public void onResponseSuccess(String str) { isRequestingUserItemStatus = false; } @Override public void onResponseError(int code) { isRequestingUserItemStatus = false; } }); } /** * 保存用戶按鈕更新信息 */ private void saveItemUpdateStatusToFile() { TaskExecutor.getInstance().post(new Runnable() { @Override public void run() { if (userItemUpdateRecord != null) { String path = StorageManager.getInstance().getPackageFiles() + AccountConstant.FILE_NAME_USER_ITEM_STATUS;//path爲文件路徑 FileUtil.writeObjectToPath(userItemUpdateRecord, path); } } }); }
接口請求完的服務器時間戳須要保存到文件裏,並更新本地model
public interface OnUpdateItemStatusListener { void updateItemStatus(); } private HashSet<OnUpdateItemStatusListener> mOnUpdateItemStatusListeners = new HashSet<>(); public void registerOnUpdateItemStatusListener(OnUpdateItemStatusListener listener) { mOnUpdateItemStatusListeners.add(listener); } public void unregisterOnUpdateItemStatusListener(OnUpdateItemStatusListener listener) { mOnUpdateItemStatusListeners.remove(listener); } private void dispatchOnUpdateItemStatusListener() { for (OnUpdateItemStatusListener listener : mOnUpdateItemStatusListeners) { listener.updateItemStatus(); } }
private RedPointImageView mButton1;
private RedPointImageView mButton2;
private RedPointImageView mButton3;
mButton1.setmTag(UserItemUpdateRecord.KEY_HOME);//給按鈕設置備註,可作判斷識別
mButton2.setmTag(UserItemUpdateRecord.KEY_INFORMATION);
mButton3.setmTag(UserItemUpdateRecord.KEY_ME);
MyClient.getInstance().getAccountManager().registerOnUpdateItemStatusListener(this);//註冊監聽 @Override public void updateItemStatus() {//監聽回調方法 checkAndUpdateItemStatus(); } private void checkAndUpdateItemStatus(){ List<RedPointImageView> views = new ArrayList<>(); views.add(mButton1); views.add(mButton2); views.add(mButton3); MyUtils.updateRedPointItem(getActivity(),views);//調用工具類中的方法 } @Override public void onDestroy() { super.onDestroy(); IpinClient.getInstance().getAccountManager().unregisterOnUpdateItemStatusListener(this);//註銷監聽 } //點擊時按鈕調用方法 mButton.updateItem();
/** * 檢查更新按鈕紅點狀態 * @param imageView */ public static void updateRedPointItem(List<RedPointImageView> imageView){ for (RedPointImageView view : imageView){ updateRedPointItem(view); } } public static void updateRedPointItem(RedPointImageView imageView){ UserItemUpdateRecord userItemUpdateRecord = IpinClient.getInstance().getAccountManager().getUserItemUpdateRecord(); if(userItemUpdateRecord.isNeedUpdate(imageView)){ imageView.setShow(true); } }
public class UserItemUpdateRecord implements Serializable, IParse { private static final String KEY_INDEX = "index"; //這裏拿了三個按鈕做爲例子,每個按鈕須要配置一個字符串(Json轉換)和 //三個參數(服務器時間戳mDSystemXX、移動端時間戳mDXX、移動端臨時時間戳mDTempXX,這個臨時時間戳的做用前面已經說明) public static final String KEY_HOME = "home"; public static final String KEY_INFORMATION = "information"; public static final String KEY_ME = "me"; private int index; private Date mDHome; private Date mDTempHome; private Date mDSystemHome; private Date mDInformation; private Date mDTempInformation; private Date mDSystemInformation; private Date mDMe; private Date mDTempMe; private Date mDSystemMe; public UserItemUpdateRecord() { mDTempHome = getDateForTemp("2015-01-01 01:01:01"); mDTempInformation = getDateForTemp("2015-01-01 01:01:01"); mDTempMe = getDateForTemp("2015-01-01 01:01:01"); mDHome = new Date(); mDTempHome = new Date(); mDSystemHome = new Date(); } public void decode(JSONObject object){ if(index == object.getInteger(KEY_INDEX))return; index = object.getInteger(KEY_INDEX); mDSystemHome = object.getDate(KEY_HOME); mDSystemInformation = object.getDate(KEY_INFORMATION); mDSystemMe = object.getDate(KEY_ME); if(mDHome==null)mDHome = mDSystemHome; if(mDInformation==null)mDInformation = mDSystemInformation; if(mDMe==null)mDMe = mDSystemMe; } @Override public JSONObject encode(Object o) { return null; } @Override public void release() { } /** * 判斷是否須要顯示紅點 * @param imageView * @return */ public boolean isNeedUpdate(RedPointImageView imageView){ String tag = imageView.getmTag(); switch (tag){ case KEY_HOME: return judgeIsNeedUpdate(imageView,mDHome,mDTempHome,mDSystemHome); case KEY_INFORMATION: return judgeIsNeedUpdate(imageView,mDInformation,mDTempInformation,mDSystemInformation); case KEY_ME: return judgeIsNeedUpdate(imageView,mDMe,mDTempMe,mDSystemMe); default: return false; } } /** * 只有當mDSystem在mDLocal、mDTemp以後才須要顯示 * @param mDLocal 本地點擊時間 * @param mDTemp 點擊最新時間 * @param mDSystem 系統更新時間 * @return */ private boolean judgeIsNeedUpdate(RedPointImageView view,Date mDLocal ,Date mDTemp,Date mDSystem){ if(mDLocal.before(mDSystem)){ if(mDTemp==null)mDTemp = new Date(mDLocal.getTime()); if(mDSystem.before(mDTemp)){ //判斷方法加入刷新動做 這裏處理了前面說到的接口請求過程當中點擊按鈕,把時間保存在臨時Temp參數,這裏進行判斷並處理,可減小寫入文件次數。 mDLocal.setTime(mDTemp.getTime()); executeUpdate(view,mDInformation,mDTempInformation); //刷新 return false; }else{ return true; } }else{ return false; } } /** * 點擊時觸發的處理方法 * @param view */ public void refreshUpdateImg(RedPointImageView view){ String tag = view.getmTag(); switch (tag){ case KEY_HOME: executeUpdate(view,mDHome,mDTempHome); break; case KEY_INFORMATION: executeUpdate(view,mDInformation,mDTempInformation); break; case KEY_ME: executeUpdate(view,mDMe,mDTempMe); break; } } private void executeUpdate(RedPointImageView view,Date mDLocal ,Date mDTemp){ boolean flag = IpinClient.getInstance().getAccountManager().isRequestingUserItemStatus(); if(flag){ mDTemp.setTime(new Date().getTime());//只更新Temp時間,等待接口請求完刷新狀態的時候作是否要保存點擊時間的判斷 if(view.isShow()){ view.setShow(false); } }else{ if(view.isShow()){ //接口已經請求完 而且處於紅點顯示狀態,使紅點不顯示,而且保存當前點擊時間 mDLocal.setTime(new Date().getTime()); IpinClient.getInstance().getAccountManager().saveItemUpdateStatusToFile(); view.setShow(false); }else{ //接口已經請求完 而且處於紅點不顯示狀態,不作時間保存處理 } } } private Date getDateForTemp(String time){ Date date = new Date(); SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); try { date = df.parse(time); } catch (ParseException e) { e.printStackTrace(); } return date; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } public Date getmDHome() { return mDHome; } public void setmDHome(Date mDHome) { this.mDHome = mDHome; } public Date getmDInformation() { return mDInformation; } public void setmDInformation(Date mDInformation) { this.mDInformation = mDInformation; } public Date getmDMe() { return mDMe; } public void setmDMe(Date mDMe) { this.mDMe = mDMe; } public Date getmDSystemMe() { return mDSystemMe; } public void setmDSystemMe(Date mDSystemMe) { this.mDSystemMe = mDSystemMe; } public Date getmDSystemHome() { return mDSystemHome; } public void setmDSystemHome(Date mDSystemHome) { this.mDSystemHome = mDSystemHome; } public Date getmDSystemInformation() { return mDSystemInformation; } public void setmDSystemInformation(Date mDSystemInformation) { this.mDSystemInformation = mDSystemInformation; } }