• 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;
                }
        }
  • 相关阅读:
    数据库备份 DBS(Database Backup),知识点
    关系型数据库 RDS(Relational Database Service),知识点
    对象存储服务 OSS(Object Storage Service),知识点(待补充上仓库代码)
    Java 为什么需要包装类,如何使用包装类?
    for each 语句
    缓存中,2个注解:@cacheable 与 @cacheput 的区别
    微信小程序,相关代码
    微信小程序中的事件
    通俗易懂:索引、单列索引、复合索引、主键、唯一索引、聚簇索引、非聚簇索引、唯一聚簇索引 的区别与联系
    MySQL 的各种 join
  • 原文地址:https://www.cnblogs.com/Sweet-Candy/p/5669815.html
Copyright © 2020-2023  润新知