什么是红点更新提示?

红点更新提示类似微信朋友圈有新的朋友消息 时会在“发现”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. codeforces Gym 100500H A. Potion of Immortality 简单DP

    Problem H. ICPC QuestTime Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100500/a ...

  2. delphi 2010 资源文件使用

    Project              Recources...   //1提取出资源 procedure TForm1.Button1Click(Sender: TObject);begin  w ...

  3. C#二维数组(矩形数组,交错数组)

    C# 支持一维数组.多维数组(矩形数组)和数组的数组(交错的数组) 1.多维数组 声明:string[,] names; 初始化:int[,] numbers = new int[3, 2] { {1 ...

  4. C语言经典算法100例(二)

    11.判断某一年是否是闰年. //判断某一年份是否是闰年 int IsLeapYear(int year) { return (year % 400 == 0 || (year % 4 == 0) & ...

  5. [Javascript] Maybe Functor

    In normal Javascript, we do undefine check or null check: , name: "Suvi"}; var name = pers ...

  6. android 自定义按钮实现 home键 和返回键

    由于在自己做的东西中用到了就总结一下,自己做了测试 在一个程序运行中如果按 返回键  分别执行了 : onpause()     onStop()   onDestory()方法 如果点击 home键 ...

  7. Html&CSS 今日心得

    今天和秋秋一起review了一下我自己写的登录页面.她给我提了几个point,对我很有启发. css样式的代码和html代码分离. 我自己做的时候是在google console里面调好了样式以后就直 ...

  8. Zend Studio / Ecliplse插件StartExplorer

    Install site.zip (quick and simple way) Locate zip file under site\target in Project Explorer, Start ...

  9. pt-online-schema-change使用说明、限制与比较

    http://seanlook.com/2016/05/27/mysql-pt-online-schema-change/ http://blog.itpub.net/22664653/viewspa ...

  10. Metadata Lock原理7

    http://blog.itpub.net/22664653/viewspace-1791744/ 一 简介 通过前面两篇文章的介绍,相信读到这里的各位对MDL 锁已经有了比较深入的了解了,本文将结合 ...