Android應用中-更新提示顯示紅點的方案

什麼是紅點更新提示?

紅點更新提示相似微信朋友圈有新的朋友消息 時會在「發現」tab上顯示紅點,表示有新的消息。

目前三種顯示方式:前端

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,
        }
    ]                  
}

RedPointImageView 

這裏紅點控件根據數量分紅三類,數量判斷標準本身定。

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();

 

工具類MyUtils中的方法

    /**
    * 檢查更新按鈕紅點狀態
    * @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);
        }
    }

 

UserItemUpdateRecord類

    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;
            }
    }
相關文章
相關標籤/搜索