栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 移动开发 > Android

Android仿QQ空间动态界面分享功能

Android 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Android仿QQ空间动态界面分享功能

先看看效果:

用极少的代码实现了 动态详情 及 二级评论 的 数据获取与处理 和 UI显示与交互,并且高解耦、高复用、高灵活。

动态列表界面MomentListFragment支持 下拉刷新与上拉加载 和 模糊搜索,反复快速滑动仍然非常流畅。

缓存机制使得数据可在启动界面后瞬间加载完成。

动态详情界面MomentActivity支持 (取消)点赞、(删除)评论、点击姓名跳到个人详情 等。

只有1张图片时图片放大显示,超过1张则按九宫格显示。

用到的CommentContainerView和MomentView都是独立的组件,既可单独使用,也可用于ListView或添加至其它ViewGroup等。

CommentContainerView复用

CommentContainerView.java

setonCommentClickListener    : 设置点击评论监听
createView    : 创建View
bindView     : 绑定数据并显示View
setMaxShowCount  : 设置最多显示数量,超过则折叠
setComment    : 设置评论
addCommentView  : 添加评论View

 package apijson.demo.client.view;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
import apijson.demo.client.R;
import apijson.demo.client.model.CommentItem;
import apijson.demo.client.view.CommentView.OnCommentClickListener;
import zuo.biao.library.base.baseView;
import zuo.biao.library.util.Log;
import zuo.biao.library.util.StringUtil;

public class CommentContainerView extends baseView> {
  private static final String TAG = "CommentContainerView";
  private onCommentClickListener onCommentClickListener;
  
  public void setonCommentClickListener(onCommentClickListener onCommentClickListener) {
    this.onCommentClickListener = onCommentClickListener;
  }
  public CommentContainerView(Activity context, Resources resources) {
    super(context, resources);
  }
  private LayoutInflater inflater;
  public ViewGroup llCommentContainerViewContainer;
  public View tvCommentContainerViewMore;
  @SuppressLint("InflateParams")
  @Override
  public View createView(LayoutInflater inflater) {
    this.inflater = inflater;
    convertView = inflater.inflate(R.layout.comment_container_view, null);
    llCommentContainerViewContainer = findViewById(R.id.llCommentContainerViewContainer);
    tvCommentContainerViewMore = findViewById(R.id.tvCommentContainerViewMore);
    return convertView;
  }
  @Override
  public void bindView(List list){
    llCommentContainerViewContainer.setVisibility(list == null || list.isEmpty() ? View.GONE : View.VISIBLE);
    if (list == null) {
      Log.w(TAG, "bindView data_ == null >> data_ = new List();");
      list = new ArrayList();
    }
    this.data = list;
    // 评论
    setComment(list);
  }
  private int maxShowCount = 3;
  
  public void setMaxShowCount(int maxShowCount) {
    this.maxShowCount = maxShowCount;
  }
  
  public void setComment(List list) {
    int count = list == null ? 0 : list.size();
    boolean showMore = maxShowCount > 0 && count > maxShowCount;
    tvCommentContainerViewMore.setVisibility(showMore ? View.VISIBLE : View.GONE);
    llCommentContainerViewContainer.removeAllViews();
    llCommentContainerViewContainer.setVisibility(count <= 0 ? View.GONE : View.VISIBLE);
    if (count > 0) {
      if (showMore) {
 list = list.subList(0, maxShowCount);
      }
      for (int i = 0; i < list.size(); i++) {
 addCommentView(i, list.get(i));
      }
    }
  }
  
  @SuppressLint("InflateParams")
  private void addCommentView(final int index, final CommentItem comment) {
    if (comment == null) {
      Log.e(TAG, "addCommentView comment == null >> return; ");
      return;
    }
    String content = StringUtil.getTrimedString(comment.getComment().getContent());
    if (StringUtil.isNotEmpty(content, true) == false) {
      Log.e(TAG, "addCommentView StringUtil.isNotEmpty(content, true) == false >> return; ");
      return;
    }
    CommentTextView commentView = (CommentTextView) inflater.inflate(R.layout.comment_item, null);
    commentView.setView(comment);
    if (onCommentClickListener != null) {
      commentView.setonClickListener(new onClickListener() {
 @Override
 public void onClick(View v) {
   onCommentClickListener.onCommentClick(comment, position, index, false);
 }
      });
      commentView.setonLongClickListener(new onLongClickListener() {
 @Override
 public boolean onLongClick(View v) {
   onCommentClickListener.onCommentClick(comment, position, index, true);
   return true;
 }
      });
    }
    llCommentContainerViewContainer.addView(commentView);
  }
}

comment_container_view.xml



  
  
  

MomentView复用

MomentView.java

setonPictureClickListener    : 设置点击图片监听
createView    : 创建View
bindView     : 绑定数据并显示View
setPraise     : 设置点赞
setShowComment  : 设置是否显示评论
getShowComment  : 获取是否显示评论的设置
setComment    : 设置评论
setPicture    : 设置九宫格图片
toComment     : 跳转到所有评论界面
getdata: 获取动态绑定的数据
isLoggedIn    : 判断是否已登录,未登录则跳到登录界面
praise      : (取消)点赞
onDialogButtonClick: 处理对话框返回结果,比如删除动态
onHttpResponse  : 处理Http请求的返回结果,比如点赞
onClick      : 处理点击事件,比如点击内容跳到动态详情界面
onItemClick    : 处理点击图片的事件,默认是查看大图,可setOnPictureClickListener接管处理

package apijson.demo.client.view;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import apijson.demo.client.R;
import apijson.demo.client.activity_fragment.LoginActivity;
import apijson.demo.client.activity_fragment.MomentActivity;
import apijson.demo.client.activity_fragment.UserActivity;
import apijson.demo.client.activity_fragment.UserListActivity;
import apijson.demo.client.application.APIJSONApplication;
import apijson.demo.client.model.CommentItem;
import apijson.demo.client.model.Moment;
import apijson.demo.client.model.MomentItem;
import apijson.demo.client.model.User;
import apijson.demo.client.util.HttpRequest;
import apijson.demo.client.view.CommentView.OnCommentClickListener;
import zuo.biao.apijson.JSONResponse;
import zuo.biao.library.base.baseView;
import zuo.biao.library.manager.CacheManager;
import zuo.biao.library.manager.HttpManager.OnHttpResponseListener;
import zuo.biao.library.model.Entry;
import zuo.biao.library.ui.alertDialog;
import zuo.biao.library.ui.alertDialog.OnDialogButtonClickListener;
import zuo.biao.library.ui.GridAdapter;
import zuo.biao.library.ui.WebViewActivity;
import zuo.biao.library.util.ImageLoaderUtil;
import zuo.biao.library.util.Log;
import zuo.biao.library.util.ScreenUtil;
import zuo.biao.library.util.StringUtil;
import zuo.biao.library.util.TimeUtil;

public class MomentView extends baseView implements onClickListener
, OnHttpResponseListener, OnDialogButtonClickListener, onItemClickListener {
  private static final String TAG = "MomentView";
  public interface onPictureClickListener {
    void onClickPicture(int momentPosition, MomentView momentView, int pictureIndex);
  }
  private onPictureClickListener onPictureClickListener;
  
  public void setonPictureClickListener(onPictureClickListener onPictureClickListener) {
    this.onPictureClickListener = onPictureClickListener;
  }
  public MomentView(Activity context, Resources resources) {
    super(context, resources);
  }
  //UI显示区(操作UI,但不存在数据获取或处理代码,也不存在事件监听代码)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  private LayoutInflater inflater;
  public View llMomentViewContainer;
  public ImageView ivMomentViewHead;
  public TextView tvMomentViewName;
  public TextView tvMomentViewStatus;
  public TextView tvMomentViewContent;
  public GridView gvMomentView;
  public TextView tvMomentViewDate;
  public ImageView ivMomentViewPraise;
  public ImageView ivMomentViewComment;
  public ViewGroup llMomentViewPraise;
  public PraiseTextView tvMomentViewPraise;
  public View vMomentViewDivider;
  public ViewGroup llMomentViewCommentContainer;
  @SuppressLint("InflateParams")
  @Override
  public View createView(LayoutInflater inflater) {
    this.inflater = inflater;
    convertView = inflater.inflate(R.layout.moment_view, null);
    llMomentViewContainer = findViewById(R.id.llMomentViewContainer);
    ivMomentViewHead = findViewById(R.id.ivMomentViewHead, this);
    tvMomentViewName = findViewById(R.id.tvMomentViewName, this);
    tvMomentViewStatus = findViewById(R.id.tvMomentViewStatus, this);
    tvMomentViewContent = findViewById(R.id.tvMomentViewContent, this);
    gvMomentView = findViewById(R.id.gvMomentView);
    tvMomentViewDate = findViewById(R.id.tvMomentViewDate);
    ivMomentViewPraise = findViewById(R.id.ivMomentViewPraise, this);
    ivMomentViewComment = findViewById(R.id.ivMomentViewComment, this);
    llMomentViewPraise = findViewById(R.id.llMomentViewPraise, this);
    tvMomentViewPraise = findViewById(R.id.tvMomentViewPraise, this);
    vMomentViewDivider = findViewById(R.id.vMomentViewDivider);
    llMomentViewCommentContainer = findViewById(R.id.llMomentViewCommentContainer);
    return convertView;
  }
  private User user;
  private Moment moment;
  private long momentId;
  private long userId;
  private boolean isCurrentUser;
  private int status;
  public int getStatus() {
    return status;
  }
  @Override
  public void bindView(MomentItem data_){
    this.data = data_;
    llMomentViewContainer.setVisibility(data == null ? View.GONE : View.VISIBLE);
    if (data == null) {
      Log.w(TAG, "bindView data == null >> return;");
      return;
    }
    this.user = data.getUser();
    this.moment = data.getMoment();
    this.momentId = moment.getId();
    this.userId = moment.getUserId();
    this.isCurrentUser = APIJSONApplication.getInstance().isCurrentUser(moment.getUserId());
    this.status = data.getMyStatus();
    ImageLoaderUtil.loadImage(ivMomentViewHead, user.getHead());
    tvMomentViewName.setText(StringUtil.getTrimedString(user.getName()));
    tvMomentViewStatus.setText(StringUtil.getTrimedString(data.getStatusString()));
    tvMomentViewStatus.setVisibility(isCurrentUser ? View.VISIBLE : View.GONE);
    tvMomentViewContent.setVisibility(StringUtil.isNotEmpty(moment.getContent(), true) ? View.VISIBLE : View.GONE);
    tvMomentViewContent.setText(StringUtil.getTrimedString(moment.getContent()));
    tvMomentViewDate.setText(TimeUtil.getSmartDate(moment.getDate()));
    // 图片
    setPicture(moment.getPictureList());
    // 点赞
    setPraise(data.getIsPraised(), data.getUserList());
    // 评论
    setComment(data.getCommentItemList());
    vMomentViewDivider.setVisibility(llMomentViewPraise.getVisibility() == View.VISIBLE
 && llMomentViewCommentContainer.getVisibility() == View.VISIBLE ? View.VISIBLE : View.GONE);
  }
  
  private void setPraise(boolean joined, List list) {
    ivMomentViewPraise.setImageResource(joined ? R.drawable.praised : R.drawable.praise);
    llMomentViewPraise.setVisibility(list == null || list.isEmpty() ? View.GONE : View.VISIBLE);
    if (llMomentViewPraise.getVisibility() == View.VISIBLE) {
      tvMomentViewPraise.setView(list);
    }
  }
  private boolean showComment = true;
  public void setShowComment(boolean showComment) {
    this.showComment = showComment;
  }
  public boolean getShowComment() {
    return showComment;
  }
  public CommentContainerView commentContainerView;
  
  public void setComment(List list) {
    llMomentViewCommentContainer.setVisibility(showComment == false || list == null || list.isEmpty()
 ? View.GONE : View.VISIBLE);
    if (llMomentViewCommentContainer.getVisibility() != View.VISIBLE) {
      Log.i(TAG, "setComment llMomentViewCommentContainer.getVisibility() != View.VISIBLE >> return;");
      return;
    }
    if (commentContainerView == null) {
      commentContainerView = new CommentContainerView(context, resources);
      llMomentViewCommentContainer.removeAllViews();
      llMomentViewCommentContainer.addView(commentContainerView.createView(inflater));
      commentContainerView.setonCommentClickListener(new onCommentClickListener() {
 @Override
 public void onCommentClick(CommentItem item, int position, int index, boolean isLong) {
   toComment(item, true);
 }
      });
      commentContainerView.tvCommentContainerViewMore.setonClickListener(this);
      commentContainerView.setMaxShowCount(5);
    }
    commentContainerView.bindView(list);
  }
  private GridAdapter adapter;
  
  private void setPicture(List pictureList) {
    List> keyValueList = new ArrayList>();
    if (pictureList != null) {
      for (String picture : pictureList) {
 keyValueList.add(new Entry(picture, null));
      }
    }
    int pictureNum = keyValueList.size();
    gvMomentView.setVisibility(pictureNum <= 0 ? View.GONE : View.VISIBLE);
    if (pictureNum <= 0) {
      Log.i(TAG, "setList pictureNum <= 0 >> lvModel.setAdapter(null); return;");
      adapter = null;
      gvMomentView.setAdapter(null);
      return;
    }
    gvMomentView.setNumColumns(pictureNum <= 1 ? 1 : 3);
    if (adapter == null) {
      adapter = new GridAdapter(context).setHasName(false);
      gvMomentView.setAdapter(adapter);
    }
    adapter.refresh(keyValueList);
    gvMomentView.setonItemClickListener(this);
    final int gridViewHeight = (int) (ScreenUtil.getScreenSize(context)[0]
 - convertView.getPaddingLeft() - convertView.getPaddingRight()
 - getDimension(R.dimen.moment_view_head_width));
    try {
      if (pictureNum >= 7) {
 gvMomentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, gridViewHeight));
      } else if (pictureNum >= 4) {
 gvMomentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, (gridViewHeight*2)/3));
      } else if (pictureNum >= 2) {
 gvMomentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, gridViewHeight / 3));
      } else {
 gvMomentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
      }
    } catch (Exception e) {
      Log.e(TAG, " setPictureGrid try int gridViewHeight;...>> catch" + e.getMessage());
    }
  }
  
  private void toComment(boolean isToComment) {
    toComment(null, isToComment);
  }
  
  private void toComment(CommentItem commentItem, boolean isToComment) {
    if (commentItem == null) {
      commentItem = new CommentItem();
    }
    toActivity(MomentActivity.createIntent(context, momentId, isToComment
 , commentItem.getId(), commentItem.getUser().getId(), commentItem.getUser().getName()));
  }
  //UI显示区(操作UI,但不存在数据获取或处理代码,也不存在事件监听代码)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  //Data数据区(存在数据获取或处理代码,但不存在事件监听代码)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  @Override
  public MomentItem getData() {//bindView(null)不会使data == null
    return llMomentViewContainer.getVisibility() == View.VISIBLE ? data: null;
  }
  
  private boolean isLoggedIn() {
    boolean isLoggedIn = APIJSONApplication.getInstance().isLoggedIn();
    if (isLoggedIn == false) {
      context.startActivity(LoginActivity.createIntent(context));
      context.overridePendingTransition(R.anim.bottom_push_in, R.anim.hold);
    }
    return isLoggedIn;
  }
  
  public void praise(boolean toPraise) {
    if (data == null || toPraise == data.getIsPraised()) {
      Log.e(TAG, "praiseWork toPraise == moment.getIsPraise() >> return;");
      return;
    }
    //    setPraise(toPraise, data.getPraiseCount() + (toPraise ? 1 : -1));
    HttpRequest.praiseMoment(momentId, toPraise, toPraise ? HTTP_PRAISE : HTTP_CANCEL_PRAISE, this);
  }
  //Data数据区(存在数据获取或处理代码,但不存在事件监听代码)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  //Event事件监听区(只要存在事件监听代码就是)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  @Override
  public void onDialogButtonClick(int requestCode, boolean isPositive) {
    if (isPositive && data != null) {
      data.setMyStatus(MomentItem.STATUS_DELETING);
      bindView(data);
      HttpRequest.deleteMoment(moment.getId(), HTTP_DELETE, this);
    }
  }
  public static final int HTTP_PRAISE = 1;
  public static final int HTTP_CANCEL_PRAISE = 2;
  public static final int HTTP_DELETE = 3;
  @Override
  public void onHttpResponse(int requestCode, String result, Exception e) {
    if (data == null) {
      Log.e(TAG, "onHttpResponse data == null >> return;");
      return;
    }
    JSonResponse response = new JSonResponse(result);
    JSonResponse response2 = response.getJSonResponse(Moment.class.getSimpleName());
    boolean isSucceed = JSONResponse.isSucceed(response2);
    switch (requestCode) {
    case HTTP_PRAISE:
    case HTTP_CANCEL_PRAISE:
      if (isSucceed) {
 data.setIsPraised(requestCode == HTTP_PRAISE);
 bindView(data);
      } else {
 showShortToast((requestCode == HTTP_PRAISE ? "点赞" : "取消点赞") + "失败,请检查网络后重试");
      }
      break;
    case HTTP_DELETE:
      showShortToast(isSucceed ? R.string.delete_succeed : R.string.delete_failed);
      //只对adapter.getCount()有影响。目前是隐藏的,不需要通知,也不需要刷新adapter,用户手动刷新后自然就更新了。
      if (isSucceed) {
 bindView(null);
 status = MomentItem.STATUS_DELETED;
 if (onDataChangedListener != null) {
   onDataChangedListener.onDataChanged();
 }
 CacheManager.getInstance().remove(MomentItem.class, "" + momentId);
      } else {
 data.setMyStatus(MomentItem.STATUS_NORMAL);
 bindView(data);
      }
      break;
    }
  }
  @Override
  public void onClick(View v) {
    if (data == null) {
      return;
    }
    if (status == MomentItem.STATUS_PUBLISHING) {
      showShortToast(R.string.publishing);
      return;
    }
    switch (v.getId()) {
    case R.id.ivMomentViewHead:
    case R.id.tvMomentViewName:
      toActivity(UserActivity.createIntent(context, userId));
      break;
    case R.id.tvMomentViewStatus:
      if (status == MomentItem.STATUS_NORMAL) {
 new alertDialog(context, "", "删除动态", true, 0, this).show();
      }
      break;
    case R.id.tvMomentViewContent:
    case R.id.tvCommentContainerViewMore:
      toComment(false);
      break;
    case R.id.tvMomentViewPraise:
    case R.id.llMomentViewPraise:
      toActivity(UserListActivity.createIntent(context, data.getPraiseUserIdList())
   .putExtra(UserListActivity.INTENT_TITLE, "点赞的人"));
      break;
    default:
      if (isLoggedIn() == false) {
 return;
      }
      switch (v.getId()) {
      case R.id.ivMomentViewPraise:
 praise(! data.getIsPraised());
 break;
      case R.id.ivMomentViewComment:
 toComment(true);
 break;
      default:
 break;
      }
      break;
    }
  }
  @Override
  public void onItemClick(AdapterView parent, View view, int position, long id) {
    if (status == MomentItem.STATUS_PUBLISHING) {
      showShortToast(R.string.publishing);
      return;
    }
    if (onPictureClickListener != null) {
      onPictureClickListener.onClickPicture(this.position, this, position);
    } else {
      toActivity(WebViewActivity.createIntent(context, null
   , adapter == null ? null : adapter.getItem(position).getKey()));
    }
  }
  //Event事件监听区(只要存在事件监听代码就是)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
}

moment_view.xml



  
    
      
    
    
      
 
 
      
      
      
      
 
 
 
      
      
 
   
   
 
 
 
 
      
    
  

由于这个项目使用了ZBLibrary快速开发框架,所以实现仿QQ空间和微信朋友圈的这种复杂界面只用了极少的代码,并且高解耦、高复用、高灵活。

服务端是用APIJSON(Server)工程快速搭建的,客户端App和服务端通过APIJSON-JSON传输结构协议通信,非常方便灵活,省去了大量的接口和文档!

今年RxJava特别火,在北京市场几乎是必备技能,所以我还把这个项目做了个RxJava版本,欢迎交流和指教。

实现UI的Java类:

MomentListFragment395行动态列表的获取和显示
MomentActivity  616行动态和评论列表的获取、显示和交互(评论和删除评论等)
MomentAdapter   67行 动态列表的显示
CommentAdapter  82行 评论列表的显示
MomentView    495行动态的显示和交互(各种跳转、点赞、删除等)
EmptyEventGridView56行 动态里图片的显示和交互(触摸空白处传递触摸事件到内层View)
PraiseTextView  129行动态里点赞用户的显示和交互(点击姓名跳到个人详情,点击整体跳到点赞的用户列表界面)
CommentView    153行一级评论(头像、姓名、内容)的显示和交互(回复、删除等),添加二级评论列表
CommentContainerView      172行二级评论列表的显示和交互(查看全部等)
CommentTextView  122行二级评论(姓名、内容)的显示和交互(回复、删除等)

实现UI的XML布局:

moment_activity  47行 动态和评论列表的显示
moment_view    148行动态的显示
comment_view   87行 一级评论(头像、姓名、内容)的显示
comment_container_view     20行 二级评论列表的显示
comment_item   10行 二级评论(姓名、内容)的显示

为什么没有实现MomentListFragment对应的XML布局?

因为MomentListFragment继承baseHttpListFragment,内部用XListView作为缺省列表View,所以可以不用自己实现了。

实现数据获取、提交和处理的Java类:

HttpRequest    +175行数据的获取和提交(getMoment,...,deleteComment)
CommentUtil    140行单层评论和和二级评论的处理
Comment      56行 评论数据
CommentItem    99行 评论的显示和交互数据
Moment      43行 动态数据
MomentItem    272行动态的显示和交互数据
User103行用户数据

(注:未列出的代码文件要么和动态无关,要么APIJSON或ZBLibrary已提供。server.model里的类由服务端提供)

 仿QQ空间和微信朋友圈,高解耦高复用高灵活

下载试用(测试服务器地址:139.196.140.118:8080)

APIJSONClientApp.apk

源码及文档(记得给个Star哦)

https://github.com/TommyLemon/APIJSON

以上所述是小编给大家介绍的Android仿QQ空间动态界面分享功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对考高分网网站的支持!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/158847.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号