Android应用中-更新提示显示红点的方案

什么是红点更新提示?

红点更新提示类似微信朋友圈有新的朋友消息 时会在“发现”tab上显示红点,表示有新的消息。

目前三种显示方式:

1.显示具体数字

2.只显示红点

3.显示省略,表示数量很多

方案思路:

1.显示红点:通过本地和服务器的时间戳对比,判断是否要显示红点,每个按钮都有其对应的本地时间戳和服务器时间戳。

2.隐藏红点:当红点显示时,点击把红点隐藏。 并判断是否要更新本地时间戳文件。

3.红点时间戳:时间戳需要进行缓存,方式为写入文件。

重点考虑问题:

1.点击按钮后是否要刷新该项的时间戳

2.由于更新时间戳要写入文件 所以尽快能少文件的保存

答:

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;
            }
    }
原文地址:https://www.cnblogs.com/Sweet-Candy/p/5669815.html