什么是红点更新提示?

红点更新提示类似微信朋友圈有新的朋友消息 时会在“发现”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;
}
}

Android应用中-更新提示显示红点的方案的更多相关文章

  1. Android Studio中设置提示函数用法

    Eclipse有一个很好的功能,就是当你代码调用某个android API时,鼠标移到对应的函数或者方法上,就会自动有一个悬浮窗提示该函数的说明(所包含的参数含义,该方法功能).迁移到Android ...

  2. Android: Service中创建窗口显示

    WindowManager.LayoutParams: int TYPE_SYSTEM_ALERT  Window type: system window, such as low power ale ...

  3. Android studio无法更新 提示网络连接失败

    Android studio 更新时,提示网络问题 “Connection failed. Please check your network connection and try again” 在默 ...

  4. 如何控制android系统中NavigationBar 的显示与隐藏

    我们使用的大多数android手机上的Home键,返回键以及menu键都是实体触摸感应按键.如果你用Google的Nexus4或Nexus5话,你会发现它们并没有实体按键或触摸感应按键,取而代之的是在 ...

  5. 在 android studio 中更新安卓应用版本号

    随着应用程序不断修改,版本号也应当变化.要更新安卓应用的版本号,只需要在 build.gradle(module:app) 中修改 versionCode 和 versionName 即可,也可以只改 ...

  6. eclipse,代码中有错误,项目或者java类中却不显示红叉

    修改eclipse代码提示级别1.单个项目修改项目上右键-->properties-->java compiler-->building-->enable project sp ...

  7. Eclipse,代码中有错误,项目中却不显示红叉

    ***修改eclipse 代码提示级别1.单个项目修改项目上右键-->properties-->java compiler-->building-->enable projec ...

  8. ionic ng-src 在网页显示,但是导出apk在android手机中运行不显示图片

    解决方法参照: http://stackoverflow.com/questions/29896158/load-image-using-ng-src-in-android-ionic-aplicat ...

  9. Android studio设置参数提示

    在Android studio的使用的过程中,那么就需要对当前的代码显示当前的方式做一个的提示信息,那么可以通过Android studio的的设置的方法,来对Android studio方法的提示显 ...

随机推荐

  1. 在ASP.NET MVC中的四大筛选器(Filter)及验证实现

    http://www.cnblogs.com/artech/archive/2012/08/06/action-filter.html http://www.cnblogs.com/ghhlyy/ar ...

  2. webservice 缓存机制

    本文转载:http://blog.csdn.net/zhdd1234/article/details/4555472 WebService的缓存分为两种,一种是简单的输出缓存,一种是强大的数据缓存 一 ...

  3. PostgreSQL的 initdb 源代码分析之一

    开始第一段: int main(int argc, char *argv[]) { /* * options with no short version return a low integer, t ...

  4. C++转义字符使用

    编码过程中字符串可能过长,这通常须要换行,对于换行转义字符\ ,使用时要保证\后无空格,否则会出现"error C2017:非法的转义字符 "错误 如 //  ''\"后 ...

  5. 【M4】非必要不提供default 构造方法

    1.default 构造方法意味着,没有外来信息的情况下,进行初始化,构造出一个对象.对于有些对象是很合理的,比如数值之类的对象,可以初始化为0:对于指针之类的对象,初始化为null:对于集合如vec ...

  6. MyBatis之七:使用generator工具

    可以将mybatis理解成一种半自动化orm框架,通过注解或者配置xml映射文件来手写相关sql语句,不能像我之前介绍orm的文章那样全对象化操作数据库增删改查.其实你会发现,手写配置xml映射文件是 ...

  7. C++ AppendMenu

    主题 1.  系统菜单下面添加自定义菜单 2. 3. 4. 5.         AppendMenu The AppendMenu function appends a new item to th ...

  8. delphi 获取驱动盘的卷标 号

    {获取C盘的卷标 格式化硬盘卷标改变} //GetHardDiskSerial('c:\') function GetHardDiskSerial(Drive: string): string; va ...

  9. 关于 " +new Date " 的个人见解

    今天晚上,在一个Javascript的Q群里,有人问下面这种代码是什么意思: var time = +new Date; 这段代码中,比较奇怪的是有一个加号,下面说说我个人的理解:这是对后面的对象做一 ...

  10. C读取配置文件

    #ifndef __CFG_OP_H__ #define __CFG_OP_H__ #ifdef __cplusplus extern "C" { #endif //获取配置项 i ...